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