1#!/usr/bin/env python 2# This file includes the common operations with eFuses for chips 3# 4# Copyright (C) 2020 Espressif Systems (Shanghai) PTE LTD 5# 6# This program is free software; you can redistribute it and/or modify it under 7# the terms of the GNU General Public License as published by the Free Software 8# Foundation; either version 2 of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, but WITHOUT 11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 12# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 13# 14# You should have received a copy of the GNU General Public License along with 15# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin 16# Street, Fifth Floor, Boston, MA 02110-1301 USA. 17from __future__ import division, print_function 18 19import argparse 20import json 21import sys 22 23from bitstring import BitString 24 25import esptool 26 27from . import base_fields 28from . import util 29 30 31def add_common_commands(subparsers, efuses): 32 class ActionEfuseValuePair(argparse.Action): 33 def __init__(self, option_strings, dest, nargs=None, **kwargs): 34 self._nargs = nargs 35 self._choices = kwargs.get("efuse_choices") 36 self.efuses = kwargs.get("efuses") 37 del kwargs["efuse_choices"] 38 del kwargs["efuses"] 39 super(ActionEfuseValuePair, self).__init__(option_strings, dest, nargs=nargs, **kwargs) 40 41 def __call__(self, parser, namespace, values, option_string=None): 42 def check_efuse_name(efuse_name, efuse_list): 43 if efuse_name not in self._choices: 44 raise esptool.FatalError("Invalid the efuse name '{}'. Available the efuse names: {}".format(efuse_name, self._choices)) 45 46 efuse_value_pairs = {} 47 if len(values) > 1: 48 if len(values) % 2: 49 raise esptool.FatalError("The list does not have a valid pair (name value) {}".format(values)) 50 for i in range(0, len(values), 2): 51 efuse_name, new_value = values[i:i + 2:] 52 check_efuse_name(efuse_name, self._choices) 53 check_arg = base_fields.CheckArgValue(self.efuses, efuse_name) 54 efuse_value_pairs[efuse_name] = check_arg(new_value) 55 else: 56 # For the case of compatibility, when only the efuse_name is given 57 # The fields with 'bitcount' and 'bool' types can be without new_value arg 58 efuse_name = values[0] 59 check_efuse_name(efuse_name, self._choices) 60 check_arg = base_fields.CheckArgValue(self.efuses, efuse_name) 61 efuse_value_pairs[efuse_name] = check_arg(None) 62 setattr(namespace, self.dest, efuse_value_pairs) 63 64 burn = subparsers.add_parser('burn_efuse', help='Burn the efuse with the specified name') 65 burn.add_argument('name_value_pairs', help='Name of efuse register and New value pairs to burn', 66 action=ActionEfuseValuePair, 67 nargs="+", 68 metavar="[EFUSE_NAME VALUE] [{} VALUE".format(" VALUE] [".join([e.name for e in efuses.efuses])), 69 efuse_choices=[e.name for e in efuses.efuses], 70 efuses=efuses) 71 72 read_protect_efuse = subparsers.add_parser('read_protect_efuse', help='Disable readback for the efuse with the specified name') 73 read_protect_efuse.add_argument('efuse_name', help='Name of efuse register to burn', nargs="+", 74 choices=[e.name for e in efuses.efuses if e.read_disable_bit is not None]) 75 76 write_protect_efuse = subparsers.add_parser('write_protect_efuse', help='Disable writing to the efuse with the specified name') 77 write_protect_efuse.add_argument('efuse_name', help='Name of efuse register to burn', nargs="+", 78 choices=[e.name for e in efuses.efuses if e.write_disable_bit is not None]) 79 80 burn_block_data = subparsers.add_parser('burn_block_data', help="Burn non-key data to EFUSE blocks. " 81 "(Don't use this command to burn key data for Flash Encryption or ESP32 Secure Boot V1, " 82 "as the byte order of keys is swapped (use burn_key)).") 83 add_force_write_always(burn_block_data) 84 burn_block_data.add_argument('--offset', '-o', help='Byte offset in the efuse block', type=int, default=0) 85 burn_block_data.add_argument('block', help='Efuse block to burn.', action='append', choices=efuses.BURN_BLOCK_DATA_NAMES) 86 burn_block_data.add_argument('datafile', help='File containing data to burn into the efuse block', action='append', type=argparse.FileType('rb')) 87 for _ in range(0, len(efuses.BURN_BLOCK_DATA_NAMES)): 88 burn_block_data.add_argument('block', help='Efuse block to burn.', metavar="BLOCK", nargs="?", action='append', 89 choices=efuses.BURN_BLOCK_DATA_NAMES) 90 burn_block_data.add_argument('datafile', nargs="?", help='File containing data to burn into the efuse block', 91 metavar="DATAFILE", action='append', type=argparse.FileType('rb')) 92 93 set_bit_cmd = subparsers.add_parser('burn_bit', help="Burn bit in the efuse block.") 94 add_force_write_always(set_bit_cmd) 95 set_bit_cmd.add_argument('block', help='Efuse block to burn.', choices=efuses.BURN_BLOCK_DATA_NAMES) 96 set_bit_cmd.add_argument('bit_number', help='Bit number in the efuse block [0..BLK_LEN-1]', nargs="+", type=int) 97 98 subparsers.add_parser('adc_info', help='Display information about ADC calibration data stored in efuse.') 99 100 dump_cmd = subparsers.add_parser('dump', help='Dump raw hex values of all efuses') 101 dump_cmd.add_argument('--file_name', help='Saves dump for each block into separate file. Provide the common path name /path/blk.bin,' 102 ' it will create: blk0.bin, blk1.bin ... blkN.bin. Use burn_block_data to write it back to another chip.') 103 104 summary_cmd = subparsers.add_parser('summary', help='Print human-readable summary of efuse values') 105 summary_cmd.add_argument('--format', help='Select the summary format', choices=['summary', 'json'], default='summary') 106 summary_cmd.add_argument('--file', help='File to save the efuse summary', type=argparse.FileType('w'), default=sys.stdout) 107 108 execute_scripts = subparsers.add_parser('execute_scripts', help='Executes scripts to burn at one time.') 109 execute_scripts.add_argument('scripts', help='The special format of python scripts.', nargs="+", type=argparse.FileType('r')) 110 111 subparsers.add_parser('check_error', help='Checks eFuse errors') 112 113 114def add_force_write_always(p): 115 p.add_argument('--force-write-always', help="Write the efuse even if it looks like it's already been written, or is write protected. " 116 "Note that this option can't disable write protection, or clear any bit which has already been set.", action='store_true') 117 118 119def summary(esp, efuses, args): 120 """ Print a human-readable summary of efuse contents """ 121 ROW_FORMAT = "%-50s %-50s%s = %s %s %s" 122 human_output = (args.format == 'summary') 123 json_efuse = {} 124 if args.file != sys.stdout: 125 print("Saving efuse values to " + args.file.name) 126 if human_output: 127 print(ROW_FORMAT.replace("-50", "-12") % ("EFUSE_NAME (Block)", "Description", "", "[Meaningful Value]", "[Readable/Writeable]", "(Hex Value)"), 128 file=args.file) 129 print("-" * 88, file=args.file) 130 for category in sorted(set(e.category for e in efuses), key=lambda c: c.title()): 131 if human_output: 132 print("%s fuses:" % category.title(), file=args.file) 133 for e in (e for e in efuses if e.category == category): 134 if e.efuse_type.startswith("bytes"): 135 raw = "" 136 else: 137 raw = "({})".format(e.get_bitstring()) 138 (readable, writeable) = (e.is_readable(), e.is_writeable()) 139 if readable and writeable: 140 perms = "R/W" 141 elif readable: 142 perms = "R/-" 143 elif writeable: 144 perms = "-/W" 145 else: 146 perms = "-/-" 147 base_value = e.get_meaning() 148 value = str(base_value) 149 if not readable: 150 value = value.replace("0", "?") 151 if human_output: 152 print(ROW_FORMAT % (e.get_info(), e.description[:50], "\n " if len(value) > 20 else "", value, perms, raw), file=args.file) 153 desc_len = len(e.description[50:]) 154 if desc_len: 155 desc_len += 50 156 for i in range(50, desc_len, 50): 157 print("%-50s %-50s" % ("", e.description[i:(50 + i)]), file=args.file) 158 if args.format == 'json': 159 json_efuse[e.name] = { 160 'name': e.name, 161 'value': base_value if readable else value, 162 'readable': readable, 163 'writeable': writeable, 164 'description': e.description, 165 'category': e.category, 166 'block': e.block, 167 'word': e.word, 168 'pos': e.pos, 169 'efuse_type': e.efuse_type, 170 'bit_len': e.bit_len} 171 if human_output: 172 print("", file=args.file) 173 if human_output: 174 print(efuses.summary(), file=args.file) 175 warnings = efuses.get_coding_scheme_warnings() 176 if warnings: 177 print("WARNING: Coding scheme has encoding bit error warnings", file=args.file) 178 if args.file != sys.stdout: 179 args.file.close() 180 print("Done") 181 if args.format == 'json': 182 json.dump(json_efuse, args.file, sort_keys=True, indent=4) 183 print("") 184 185 186def dump(esp, efuses, args): 187 """ Dump raw efuse data registers """ 188 # Using --debug option allows to print dump. 189 # Nothing to do here. The log will be print during EspEfuses.__init__() in self.read_blocks() 190 if args.file_name: 191 # save dump to the file 192 for block in efuses.blocks: 193 file_dump_name = args.file_name 194 place_for_index = file_dump_name.find(".bin") 195 file_dump_name = file_dump_name[:place_for_index] + str(block.id) + file_dump_name[place_for_index:] 196 print(file_dump_name) 197 with open(file_dump_name, "wb") as f: 198 block.get_bitstring().byteswap() 199 block.get_bitstring().tofile(f) 200 201 202def burn_efuse(esp, efuses, args): 203 def print_attention(blocked_efuses_after_burn): 204 if len(blocked_efuses_after_burn): 205 print(" ATTENTION! This BLOCK uses NOT the NONE coding scheme and after 'BURN', these efuses can not be burned in the feature:") 206 for i in range(0, len(blocked_efuses_after_burn), 5): 207 print(" ", "".join("{}".format(blocked_efuses_after_burn[i:i + 5:]))) 208 209 efuse_name_list = [name for name in args.name_value_pairs.keys()] 210 burn_efuses_list = [efuses[name] for name in efuse_name_list] 211 old_value_list = [efuses[name].get_raw() for name in efuse_name_list] 212 new_value_list = [value for value in args.name_value_pairs.values()] 213 util.check_duplicate_name_in_list(efuse_name_list) 214 215 attention = "" 216 print("The efuses to burn:") 217 for block in efuses.blocks: 218 burn_list_a_block = [e for e in burn_efuses_list if e.block == block.id] 219 if len(burn_list_a_block): 220 print(" from BLOCK%d" % (block.id)) 221 for field in burn_list_a_block: 222 print(" - %s" % (field.name)) 223 if efuses.blocks[field.block].get_coding_scheme() != efuses.REGS.CODING_SCHEME_NONE: 224 using_the_same_block_names = [e.name for e in efuses if e.block == field.block] 225 wr_names = [e.name for e in burn_list_a_block] 226 blocked_efuses_after_burn = [name for name in using_the_same_block_names if name not in wr_names] 227 attention = " (see 'ATTENTION!' above)" 228 if attention: 229 print_attention(blocked_efuses_after_burn) 230 231 print("\nBurning efuses{}:".format(attention)) 232 for efuse, new_value in zip(burn_efuses_list, new_value_list): 233 print("\n - '{}' ({}) {} -> {}".format(efuse.name, efuse.description, efuse.get_bitstring(), efuse.convert_to_bitstring(new_value))) 234 efuse.save(new_value) 235 if args.only_burn_at_end: 236 return 237 efuses.burn_all() 238 239 print("Checking efuses...") 240 raise_error = False 241 for efuse, old_value, new_value in zip(burn_efuses_list, old_value_list, new_value_list): 242 if not efuse.is_readable(): 243 print("Efuse %s is read-protected. Read back the burn value is not possible." % efuse.name) 244 else: 245 new_value = efuse.convert_to_bitstring(new_value) 246 burned_value = efuse.get_bitstring() 247 if burned_value != new_value: 248 print(burned_value, "->", new_value, "Efuse %s failed to burn. Protected?" % efuse.name) 249 raise_error = True 250 if raise_error: 251 raise esptool.FatalError("The burn was not successful.") 252 else: 253 print("Successful") 254 255 256def read_protect_efuse(esp, efuses, args): 257 util.check_duplicate_name_in_list(args.efuse_name) 258 259 for efuse_name in args.efuse_name: 260 efuse = efuses[efuse_name] 261 if not efuse.is_readable(): 262 print("Efuse %s is already read protected" % efuse.name) 263 else: 264 if esp.CHIP_NAME == "ESP32": 265 if efuse_name == 'BLOCK2' and not efuses['ABS_DONE_0'].get() and "revision 3" in esp.get_chip_description(): 266 if efuses['ABS_DONE_1'].get(): 267 raise esptool.FatalError("Secure Boot V2 is on (ABS_DONE_1 = True), BLOCK2 must be readable, stop this operation!") 268 else: 269 print("In case using Secure Boot V2, the BLOCK2 must be readable, please stop this operation!") 270 else: 271 for block in efuses.Blocks.BLOCKS: 272 block = efuses.Blocks.get(block) 273 if block.name == efuse_name and block.key_purpose is not None: 274 if not efuses[block.key_purpose].need_rd_protect(efuses[block.key_purpose].get()): 275 raise esptool.FatalError("%s must be readable, stop this operation!" % efuse_name) 276 break 277 # make full list of which efuses will be disabled (ie share a read disable bit) 278 all_disabling = [e for e in efuses if e.read_disable_bit == efuse.read_disable_bit] 279 names = ", ".join(e.name for e in all_disabling) 280 print("Permanently read-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "", names)) 281 efuse.disable_read() 282 283 if args.only_burn_at_end: 284 return 285 efuses.burn_all() 286 287 print("Checking efuses...") 288 raise_error = False 289 for efuse_name in args.efuse_name: 290 efuse = efuses[efuse_name] 291 if efuse.is_readable(): 292 print("Efuse %s is not read-protected." % efuse.name) 293 raise_error = True 294 if raise_error: 295 raise esptool.FatalError("The burn was not successful.") 296 else: 297 print("Successful") 298 299 300def write_protect_efuse(esp, efuses, args): 301 util.check_duplicate_name_in_list(args.efuse_name) 302 for efuse_name in args.efuse_name: 303 efuse = efuses[efuse_name] 304 if not efuse.is_writeable(): 305 print("Efuse %s is already write protected" % efuse.name) 306 else: 307 # make full list of which efuses will be disabled (ie share a write disable bit) 308 all_disabling = [e for e in efuses if e.write_disable_bit == efuse.write_disable_bit] 309 names = ", ".join(e.name for e in all_disabling) 310 print("Permanently write-disabling efuse%s %s" % ("s" if len(all_disabling) > 1 else "", names)) 311 efuse.disable_write() 312 313 if args.only_burn_at_end: 314 return 315 efuses.burn_all() 316 317 print("Checking efuses...") 318 raise_error = False 319 for efuse_name in args.efuse_name: 320 efuse = efuses[efuse_name] 321 if efuse.is_writeable(): 322 print("Efuse %s is not write-protected." % efuse.name) 323 raise_error = True 324 if raise_error: 325 raise esptool.FatalError("The burn was not successful.") 326 else: 327 print("Successful") 328 329 330def burn_block_data(esp, efuses, args): 331 block_name_list = args.block[0:len([name for name in args.block if name is not None]):] 332 datafile_list = args.datafile[0:len([name for name in args.datafile if name is not None]):] 333 efuses.force_write_always = args.force_write_always 334 335 util.check_duplicate_name_in_list(block_name_list) 336 if args.offset and len(block_name_list) > 1: 337 raise esptool.FatalError("The 'offset' option is not applicable when a few blocks are passed. With 'offset', should only one block be used.") 338 else: 339 offset = args.offset 340 if offset: 341 num_block = efuses.get_index_block_by_name(block_name_list[0]) 342 block = efuses.blocks[num_block] 343 num_bytes = block.get_block_len() 344 if offset >= num_bytes: 345 raise esptool.FatalError("Invalid offset: the block%d only holds %d bytes." % (block.id, num_bytes)) 346 if len(block_name_list) != len(datafile_list): 347 raise esptool.FatalError("The number of block_name (%d) and datafile (%d) should be the same." % (len(block_name_list), len(datafile_list))) 348 349 for block_name, datafile in zip(block_name_list, datafile_list): 350 num_block = efuses.get_index_block_by_name(block_name) 351 block = efuses.blocks[num_block] 352 data = datafile.read() 353 num_bytes = block.get_block_len() 354 if offset != 0: 355 data = (b'\x00' * offset) + data 356 data = data + (b'\x00' * (num_bytes - len(data))) 357 if len(data) != num_bytes: 358 raise esptool.FatalError("Data does not fit: the block%d size is %d bytes, data file is %d bytes, offset %d" % 359 (block.id, num_bytes, len(data), offset)) 360 print("[{:02}] {:20} size={:02} bytes, offset={:02} - > [{}].".format(block.id, block.name, len(data), offset, util.hexify(data, " "))) 361 block.save(data) 362 363 if args.only_burn_at_end: 364 return 365 efuses.burn_all() 366 print("Successful") 367 368 369def burn_bit(esp, efuses, args): 370 efuses.force_write_always = args.force_write_always 371 num_block = efuses.get_index_block_by_name(args.block) 372 block = efuses.blocks[num_block] 373 data_block = BitString(block.get_block_len() * 8) 374 data_block.set(0) 375 try: 376 data_block.set(True, args.bit_number) 377 except IndexError: 378 raise esptool.FatalError("%s has bit_number in [0..%d]" % (args.block, data_block.len - 1)) 379 data_block.reverse() 380 print("bit_number: [%-03d]........................................................[0]" % (data_block.len - 1)) 381 print("BLOCK%-2d :" % block.id, data_block) 382 block.print_block(data_block, "regs_to_write", debug=True) 383 block.save(data_block.bytes[::-1]) 384 385 if args.only_burn_at_end: 386 return 387 efuses.burn_all() 388 print("Successful") 389 390 391def check_error(esp, efuses, args): 392 if efuses.get_coding_scheme_warnings(): 393 raise esptool.FatalError("Error(s) were detected in eFuses") 394 print("No errors detected") 395