1# This file describes eFuses for ESP32-C3 chip 2# 3# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD 4# 5# SPDX-License-Identifier: GPL-2.0-or-later 6 7import binascii 8import struct 9import time 10 11from bitstring import BitArray 12 13import esptool 14 15import reedsolo 16 17from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters 18from .. import base_fields 19from .. import util 20 21 22class EfuseBlock(base_fields.EfuseBlockBase): 23 def len_of_burn_unit(self): 24 # The writing register window is 8 registers for any blocks. 25 # len in bytes 26 return 8 * 4 27 28 def __init__(self, parent, param, skip_read=False): 29 parent.read_coding_scheme() 30 super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read) 31 32 def apply_coding_scheme(self): 33 data = self.get_raw(from_read=False)[::-1] 34 if len(data) < self.len_of_burn_unit(): 35 add_empty_bytes = self.len_of_burn_unit() - len(data) 36 data = data + (b"\x00" * add_empty_bytes) 37 if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: 38 # takes 32 bytes 39 # apply RS encoding 40 rs = reedsolo.RSCodec(12) 41 # 32 byte of data + 12 bytes RS 42 encoded_data = rs.encode([x for x in data]) 43 words = struct.unpack("<" + "I" * 11, encoded_data) 44 # returns 11 words (8 words of data + 3 words of RS coding) 45 else: 46 # takes 32 bytes 47 words = struct.unpack("<" + ("I" * (len(data) // 4)), data) 48 # returns 8 words 49 return words 50 51 52class EspEfuses(base_fields.EspEfusesBase): 53 """ 54 Wrapper object to manage the efuse fields in a connected ESP bootloader 55 """ 56 57 Blocks = EfuseDefineBlocks() 58 Fields = EfuseDefineFields() 59 REGS = EfuseDefineRegisters 60 BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() 61 BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() 62 63 debug = False 64 do_not_confirm = False 65 66 def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False): 67 self._esp = esp 68 self.debug = debug 69 self.do_not_confirm = do_not_confirm 70 if esp.CHIP_NAME != "ESP32-C3": 71 raise esptool.FatalError( 72 "Expected the 'esp' param for ESP32-C3 chip but got for '%s'." 73 % (esp.CHIP_NAME) 74 ) 75 if not skip_connect: 76 flags = self._esp.get_security_info()["flags"] 77 GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2 78 if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE: 79 raise esptool.FatalError( 80 "Secure Download Mode is enabled. The tool can not read eFuses." 81 ) 82 self.blocks = [ 83 EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect) 84 for block in self.Blocks.BLOCKS 85 ] 86 if not skip_connect: 87 self.get_coding_scheme_warnings() 88 self.efuses = [ 89 EfuseField.from_tuple( 90 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 91 ) 92 for efuse in self.Fields.EFUSES 93 ] 94 self.efuses += [ 95 EfuseField.from_tuple( 96 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 97 ) 98 for efuse in self.Fields.KEYBLOCKS 99 ] 100 if skip_connect: 101 self.efuses += [ 102 EfuseField.from_tuple( 103 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 104 ) 105 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 106 ] 107 else: 108 if self["BLK_VERSION_MAJOR"].get() == 1: 109 self.efuses += [ 110 EfuseField.from_tuple( 111 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 112 ) 113 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 114 ] 115 self.efuses += [ 116 EfuseField.from_tuple( 117 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 118 ) 119 for efuse in self.Fields.CALC 120 ] 121 122 def __getitem__(self, efuse_name): 123 """Return the efuse field with the given name""" 124 for e in self.efuses: 125 if efuse_name == e.name: 126 return e 127 new_fields = False 128 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: 129 e = self.Fields.get(efuse) 130 if e.name == efuse_name: 131 self.efuses += [ 132 EfuseField.from_tuple( 133 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 134 ) 135 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 136 ] 137 new_fields = True 138 if new_fields: 139 for e in self.efuses: 140 if efuse_name == e.name: 141 return e 142 raise KeyError 143 144 def read_coding_scheme(self): 145 self.coding_scheme = self.REGS.CODING_SCHEME_RS 146 147 def print_status_regs(self): 148 print("") 149 self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True) 150 print( 151 "{:27} 0x{:08x}".format( 152 "EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG) 153 ) 154 ) 155 print( 156 "{:27} 0x{:08x}".format( 157 "EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG) 158 ) 159 ) 160 161 def get_block_errors(self, block_num): 162 """Returns (error count, failure boolean flag)""" 163 return self.blocks[block_num].num_errors, self.blocks[block_num].fail 164 165 def efuse_controller_setup(self): 166 self.set_efuse_timing() 167 self.clear_pgm_registers() 168 self.wait_efuse_idle() 169 170 def write_efuses(self, block): 171 self.efuse_program(block) 172 return self.get_coding_scheme_warnings(silent=True) 173 174 def clear_pgm_registers(self): 175 self.wait_efuse_idle() 176 for r in range( 177 self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4 178 ): 179 self.write_reg(r, 0) 180 181 def wait_efuse_idle(self): 182 deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT 183 while time.time() < deadline: 184 # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0: 185 if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: 186 return 187 raise esptool.FatalError( 188 "Timed out waiting for Efuse controller command to complete" 189 ) 190 191 def efuse_program(self, block): 192 self.wait_efuse_idle() 193 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE) 194 self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2)) 195 self.wait_efuse_idle() 196 self.clear_pgm_registers() 197 self.efuse_read() 198 199 def efuse_read(self): 200 self.wait_efuse_idle() 201 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE) 202 # need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some 203 # efuse registers after each command is completed 204 # if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip. 205 try: 206 self.write_reg( 207 self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 208 ) 209 self.wait_efuse_idle() 210 except esptool.FatalError: 211 secure_download_mode_before = self._esp.secure_download_mode 212 213 try: 214 self._esp = self.reconnect_chip(self._esp) 215 except esptool.FatalError: 216 print("Can not re-connect to the chip") 217 if not self["DIS_DOWNLOAD_MODE"].get() and self[ 218 "DIS_DOWNLOAD_MODE" 219 ].get(from_read=False): 220 print( 221 "This is the correct behavior as we are actually burning " 222 "DIS_DOWNLOAD_MODE which disables the connection to the chip" 223 ) 224 print("DIS_DOWNLOAD_MODE is enabled") 225 print("Successful") 226 exit(0) # finish without errors 227 raise 228 229 print("Established a connection with the chip") 230 if self._esp.secure_download_mode and not secure_download_mode_before: 231 print("Secure download mode is enabled") 232 if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[ 233 "ENABLE_SECURITY_DOWNLOAD" 234 ].get(from_read=False): 235 print( 236 "espefuse tool can not continue to work in Secure download mode" 237 ) 238 print("ENABLE_SECURITY_DOWNLOAD is enabled") 239 print("Successful") 240 exit(0) # finish without errors 241 raise 242 243 def set_efuse_timing(self): 244 """Set timing registers for burning efuses""" 245 # Configure clock 246 apb_freq = self.get_crystal_freq() 247 if apb_freq != 40: 248 raise esptool.FatalError( 249 "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq 250 ) 251 252 self.update_reg( 253 self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190 254 ) 255 256 def get_coding_scheme_warnings(self, silent=False): 257 """Check if the coding scheme has detected any errors.""" 258 ret_fail = False 259 for block in self.blocks: 260 if block.id == 0: 261 words = [ 262 self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) 263 for offs in range(5) 264 ] 265 data = BitArray() 266 for word in reversed(words): 267 data.append("uint:32=%d" % word) 268 # pos=32 because EFUSE_WR_DIS goes first it is 32bit long 269 # and not under error control 270 block.err_bitarray.overwrite(data, pos=32) 271 block.num_errors = block.err_bitarray.count(True) 272 block.fail = block.num_errors != 0 273 else: 274 addr_reg_f, fail_bit = self.REGS.BLOCK_FAIL_BIT[block.id] 275 if fail_bit is None: 276 block.fail = False 277 else: 278 block.fail = self.read_reg(addr_reg_f) & (1 << fail_bit) != 0 279 280 addr_reg_n, num_mask, num_offs = self.REGS.BLOCK_NUM_ERRORS[block.id] 281 if num_mask is None or num_offs is None: 282 block.num_errors = 0 283 else: 284 block.num_errors = ( 285 self.read_reg(addr_reg_n) >> num_offs 286 ) & num_mask 287 288 ret_fail |= block.fail 289 if not silent and (block.fail or block.num_errors): 290 print( 291 "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]" 292 % (block.id, block.num_errors, block.fail) 293 ) 294 if (self.debug or ret_fail) and not silent: 295 self.print_status_regs() 296 return ret_fail 297 298 def summary(self): 299 # TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)" 300 return "" 301 302 303class EfuseField(base_fields.EfuseFieldBase): 304 @staticmethod 305 def from_tuple(parent, efuse_tuple, type_class): 306 return { 307 "mac": EfuseMacField, 308 "keypurpose": EfuseKeyPurposeField, 309 "t_sensor": EfuseTempSensor, 310 "adc_tp": EfuseAdcPointCalibration, 311 "wafer": EfuseWafer, 312 }.get(type_class, EfuseField)(parent, efuse_tuple) 313 314 def get_info(self): 315 output = "%s (BLOCK%d)" % (self.name, self.block) 316 errs, fail = self.parent.get_block_errors(self.block) 317 if errs != 0 or fail: 318 output += ( 319 "[FAIL:%d]" % (fail) 320 if self.block == 0 321 else "[ERRS:%d FAIL:%d]" % (errs, fail) 322 ) 323 if self.efuse_class == "keyblock": 324 name = self.parent.blocks[self.block].key_purpose_name 325 if name is not None: 326 output += "\n Purpose: %s\n " % (self.parent[name].get()) 327 return output 328 329 330class EfuseWafer(EfuseField): 331 def get(self, from_read=True): 332 hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read) 333 lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read) 334 return (hi_bits << 3) + lo_bits 335 336 def save(self, new_value): 337 raise esptool.FatalError("Burning %s is not supported" % self.name) 338 339 340class EfuseTempSensor(EfuseField): 341 def get(self, from_read=True): 342 value = self.get_bitstring(from_read) 343 sig = -1 if value[0] else 1 344 return sig * value[1:].uint * 0.1 345 346 347class EfuseAdcPointCalibration(EfuseField): 348 def get(self, from_read=True): 349 STEP_SIZE = 4 350 value = self.get_bitstring(from_read) 351 sig = -1 if value[0] else 1 352 return sig * value[1:].uint * STEP_SIZE 353 354 355class EfuseMacField(EfuseField): 356 def check_format(self, new_value_str): 357 if new_value_str is None: 358 raise esptool.FatalError( 359 "Required MAC Address in AA:CD:EF:01:02:03 format!" 360 ) 361 if new_value_str.count(":") != 5: 362 raise esptool.FatalError( 363 "MAC Address needs to be a 6-byte hexadecimal format " 364 "separated by colons (:)!" 365 ) 366 hexad = new_value_str.replace(":", "") 367 if len(hexad) != 12: 368 raise esptool.FatalError( 369 "MAC Address needs to be a 6-byte hexadecimal number " 370 "(12 hexadecimal characters)!" 371 ) 372 # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03', 373 bindata = binascii.unhexlify(hexad) 374 # unicast address check according to 375 # https://tools.ietf.org/html/rfc7042#section-2.1 376 if esptool.util.byte(bindata, 0) & 0x01: 377 raise esptool.FatalError("Custom MAC must be a unicast MAC!") 378 return bindata 379 380 def check(self): 381 errs, fail = self.parent.get_block_errors(self.block) 382 if errs != 0 or fail: 383 output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail) 384 else: 385 output = "OK" 386 return "(" + output + ")" 387 388 def get(self, from_read=True): 389 if self.name == "CUSTOM_MAC": 390 mac = self.get_raw(from_read)[::-1] 391 else: 392 mac = self.get_raw(from_read) 393 return "%s %s" % (util.hexify(mac, ":"), self.check()) 394 395 def save(self, new_value): 396 def print_field(e, new_value): 397 print( 398 " - '{}' ({}) {} -> {}".format( 399 e.name, e.description, e.get_bitstring(), new_value 400 ) 401 ) 402 403 if self.name == "CUSTOM_MAC": 404 bitarray_mac = self.convert_to_bitstring(new_value) 405 print_field(self, bitarray_mac) 406 super(EfuseMacField, self).save(new_value) 407 else: 408 # Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible, 409 # as it's written in the factory. 410 raise esptool.FatalError("Writing Factory MAC address is not supported") 411 412 413# fmt: off 414class EfuseKeyPurposeField(EfuseField): 415 KEY_PURPOSES = [ 416 ("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use) 417 ("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved 418 ("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption) 419 ("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode 420 ("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode) 421 ("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode) 422 ("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode 423 ("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest) 424 ("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest) 425 ("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest) 426 ] 427# fmt: on 428 KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES] 429 DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"] 430 431 def check_format(self, new_value_str): 432 # str convert to int: "XTS_AES_128_KEY" - > str(4) 433 # if int: 4 -> str(4) 434 raw_val = new_value_str 435 for purpose_name in self.KEY_PURPOSES: 436 if purpose_name[0] == new_value_str: 437 raw_val = str(purpose_name[1]) 438 break 439 if raw_val.isdigit(): 440 if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]: 441 raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val) 442 else: 443 raise esptool.FatalError("'%s' unknown name" % raw_val) 444 return raw_val 445 446 def need_reverse(self, new_key_purpose): 447 for key in self.KEY_PURPOSES: 448 if key[0] == new_key_purpose: 449 return key[3] == "Reverse" 450 451 def need_rd_protect(self, new_key_purpose): 452 for key in self.KEY_PURPOSES: 453 if key[0] == new_key_purpose: 454 return key[4] == "need_rd_protect" 455 456 def get(self, from_read=True): 457 for p in self.KEY_PURPOSES: 458 if p[1] == self.get_raw(from_read): 459 return p[0] 460 return "FORBIDDEN_STATE" 461 462 def save(self, new_value): 463 raw_val = int(self.check_format(str(new_value))) 464 return super(EfuseKeyPurposeField, self).save(raw_val) 465