1# This file describes eFuses for ESP32-P4 chip 2# 3# SPDX-FileCopyrightText: 2023 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-P4": 78 raise esptool.FatalError( 79 "Expected the 'esp' param for ESP32-P4 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 # TODO add processing of self.Fields.BLOCK2_CALIBRATION_EFUSES 106 # if self["BLK_VERSION_MINOR"].get() == 1: 107 # self.efuses += [ 108 # EfuseField.convert(self, efuse) 109 # for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 110 # ] 111 self.efuses += [ 112 EfuseField.convert(self, efuse) for efuse in self.Fields.CALC 113 ] 114 115 def __getitem__(self, efuse_name): 116 """Return the efuse field with the given name""" 117 for e in self.efuses: 118 if efuse_name == e.name or any(x == efuse_name for x in e.alt_names): 119 return e 120 new_fields = False 121 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: 122 if efuse.name == efuse_name or any( 123 x == efuse_name for x in efuse.alt_names 124 ): 125 self.efuses += [ 126 EfuseField.convert(self, efuse) 127 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 128 ] 129 new_fields = True 130 if new_fields: 131 for e in self.efuses: 132 if efuse_name == e.name or any(x == efuse_name for x in e.alt_names): 133 return e 134 raise KeyError 135 136 def read_coding_scheme(self): 137 self.coding_scheme = self.REGS.CODING_SCHEME_RS 138 139 def print_status_regs(self): 140 print("") 141 self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True) 142 print( 143 "{:27} 0x{:08x}".format( 144 "EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG) 145 ) 146 ) 147 print( 148 "{:27} 0x{:08x}".format( 149 "EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG) 150 ) 151 ) 152 153 def efuse_controller_setup(self): 154 self.set_efuse_timing() 155 self.clear_pgm_registers() 156 self.wait_efuse_idle() 157 158 def write_efuses(self, block): 159 self.efuse_program(block) 160 return self.get_coding_scheme_warnings(silent=True) 161 162 def clear_pgm_registers(self): 163 self.wait_efuse_idle() 164 for r in range( 165 self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4 166 ): 167 self.write_reg(r, 0) 168 169 def wait_efuse_idle(self): 170 deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT 171 while time.time() < deadline: 172 cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD 173 if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0: 174 if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0: 175 # Due to a hardware error, we have to read READ_CMD again 176 # to make sure the efuse clock is normal. 177 # For PGM_CMD it is not necessary. 178 return 179 raise esptool.FatalError( 180 "Timed out waiting for Efuse controller command to complete" 181 ) 182 183 def efuse_program(self, block): 184 self.wait_efuse_idle() 185 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE) 186 self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2)) 187 self.wait_efuse_idle() 188 self.clear_pgm_registers() 189 self.efuse_read() 190 191 def efuse_read(self): 192 self.wait_efuse_idle() 193 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE) 194 # need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some 195 # efuse registers after each command is completed 196 # if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip. 197 try: 198 self.write_reg( 199 self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 200 ) 201 self.wait_efuse_idle() 202 except esptool.FatalError: 203 secure_download_mode_before = self._esp.secure_download_mode 204 205 try: 206 self._esp = self.reconnect_chip(self._esp) 207 except esptool.FatalError: 208 print("Can not re-connect to the chip") 209 if not self["DIS_DOWNLOAD_MODE"].get() and self[ 210 "DIS_DOWNLOAD_MODE" 211 ].get(from_read=False): 212 print( 213 "This is the correct behavior as we are actually burning " 214 "DIS_DOWNLOAD_MODE which disables the connection to the chip" 215 ) 216 print("DIS_DOWNLOAD_MODE is enabled") 217 print("Successful") 218 sys.exit(0) # finish without errors 219 raise 220 221 print("Established a connection with the chip") 222 if self._esp.secure_download_mode and not secure_download_mode_before: 223 print("Secure download mode is enabled") 224 if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[ 225 "ENABLE_SECURITY_DOWNLOAD" 226 ].get(from_read=False): 227 print( 228 "espefuse tool can not continue to work in Secure download mode" 229 ) 230 print("ENABLE_SECURITY_DOWNLOAD is enabled") 231 print("Successful") 232 sys.exit(0) # finish without errors 233 raise 234 235 def set_efuse_timing(self): 236 """Set timing registers for burning efuses""" 237 # Configure clock 238 apb_freq = self.get_crystal_freq() 239 if apb_freq != 40: 240 raise esptool.FatalError( 241 "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq 242 ) 243 # keep default timing settings 244 245 def get_coding_scheme_warnings(self, silent=False): 246 """Check if the coding scheme has detected any errors.""" 247 old_addr_reg = 0 248 reg_value = 0 249 ret_fail = False 250 for block in self.blocks: 251 if block.id == 0: 252 words = [ 253 self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) 254 for offs in range(5) 255 ] 256 block.err_bitarray.pos = 0 257 for word in reversed(words): 258 block.err_bitarray.overwrite(BitArray("uint:32=%d" % word)) 259 block.num_errors = block.err_bitarray.count(True) 260 block.fail = block.num_errors != 0 261 else: 262 addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[ 263 block.id 264 ] 265 if err_num_mask is None or err_num_offs is None or fail_bit is None: 266 continue 267 if addr_reg != old_addr_reg: 268 old_addr_reg = addr_reg 269 reg_value = self.read_reg(addr_reg) 270 block.fail = reg_value & (1 << fail_bit) != 0 271 block.num_errors = (reg_value >> err_num_offs) & err_num_mask 272 ret_fail |= block.fail 273 if not silent and (block.fail or block.num_errors): 274 print( 275 "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]" 276 % (block.id, block.num_errors, block.fail) 277 ) 278 if (self.debug or ret_fail) and not silent: 279 self.print_status_regs() 280 return ret_fail 281 282 def summary(self): 283 # TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)" 284 return "" 285 286 287class EfuseField(base_fields.EfuseFieldBase): 288 @staticmethod 289 def convert(parent, efuse): 290 return { 291 "mac": EfuseMacField, 292 "keypurpose": EfuseKeyPurposeField, 293 "t_sensor": EfuseTempSensor, 294 "adc_tp": EfuseAdcPointCalibration, 295 }.get(efuse.class_type, EfuseField)(parent, efuse) 296 297 298class EfuseTempSensor(EfuseField): 299 def get(self, from_read=True): 300 value = self.get_bitstring(from_read) 301 sig = -1 if value[0] else 1 302 return sig * value[1:].uint * 0.1 303 304 305class EfuseAdcPointCalibration(EfuseField): 306 def get(self, from_read=True): 307 STEP_SIZE = 4 308 value = self.get_bitstring(from_read) 309 sig = -1 if value[0] else 1 310 return sig * value[1:].uint * STEP_SIZE 311 312 313class EfuseMacField(EfuseField): 314 def check_format(self, new_value_str): 315 if new_value_str is None: 316 raise esptool.FatalError( 317 "Required MAC Address in AA:CD:EF:01:02:03 format!" 318 ) 319 num_bytes = 8 if self.name == "MAC_EUI64" else 6 320 if new_value_str.count(":") != num_bytes - 1: 321 raise esptool.FatalError( 322 f"MAC Address needs to be a {num_bytes}-byte hexadecimal format " 323 "separated by colons (:)!" 324 ) 325 hexad = new_value_str.replace(":", "").split(" ", 1)[0] 326 hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad 327 if len(hexad) != num_bytes * 2: 328 raise esptool.FatalError( 329 f"MAC Address needs to be a {num_bytes}-byte hexadecimal number " 330 f"({num_bytes * 2} hexadecimal characters)!" 331 ) 332 # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03', 333 bindata = binascii.unhexlify(hexad) 334 335 if not self.is_field_calculated(): 336 # unicast address check according to 337 # https://tools.ietf.org/html/rfc7042#section-2.1 338 if esptool.util.byte(bindata, 0) & 0x01: 339 raise esptool.FatalError("Custom MAC must be a unicast MAC!") 340 return bindata 341 342 def check(self): 343 errs, fail = self.parent.get_block_errors(self.block) 344 if errs != 0 or fail: 345 output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail) 346 else: 347 output = "OK" 348 return "(" + output + ")" 349 350 def get(self, from_read=True): 351 if self.name == "CUSTOM_MAC": 352 mac = self.get_raw(from_read)[::-1] 353 elif self.name == "MAC": 354 mac = self.get_raw(from_read) 355 elif self.name == "MAC_EUI64": 356 mac = self.parent["MAC"].get_bitstring(from_read).copy() 357 mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read) 358 mac.insert(mac_ext, 24) 359 mac = mac.bytes 360 else: 361 mac = self.get_raw(from_read) 362 return "%s %s" % (util.hexify(mac, ":"), self.check()) 363 364 def save(self, new_value): 365 def print_field(e, new_value): 366 print( 367 " - '{}' ({}) {} -> {}".format( 368 e.name, e.description, e.get_bitstring(), new_value 369 ) 370 ) 371 372 if self.name == "CUSTOM_MAC": 373 bitarray_mac = self.convert_to_bitstring(new_value) 374 print_field(self, bitarray_mac) 375 super(EfuseMacField, self).save(new_value) 376 else: 377 # Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible, 378 # as it's written in the factory. 379 raise esptool.FatalError(f"Burning {self.name} is not supported") 380 381 382# fmt: off 383class EfuseKeyPurposeField(EfuseField): 384 KEY_PURPOSES = [ 385 ("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use) 386 ("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key 387 ("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption) 388 ("XTS_AES_256_KEY_2", 3, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_2 (flash/PSRAM encryption) 389 ("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption) 390 ("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode 391 ("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode) 392 ("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode) 393 ("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode 394 ("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest) 395 ("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest) 396 ("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest) 397 ("KM_INIT_KEY", 12, None, None, "need_rd_protect"), # init key that is used for the generation of AES/ECDSA key 398 ("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 399 ] 400# fmt: on 401 KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES] 402 DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"] 403 404 def check_format(self, new_value_str): 405 # str convert to int: "XTS_AES_128_KEY" - > str(4) 406 # if int: 4 -> str(4) 407 raw_val = new_value_str 408 for purpose_name in self.KEY_PURPOSES: 409 if purpose_name[0] == new_value_str: 410 raw_val = str(purpose_name[1]) 411 break 412 if raw_val.isdigit(): 413 if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]: 414 raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val) 415 else: 416 raise esptool.FatalError("'%s' unknown name" % raw_val) 417 return raw_val 418 419 def need_reverse(self, new_key_purpose): 420 for key in self.KEY_PURPOSES: 421 if key[0] == new_key_purpose: 422 return key[3] == "Reverse" 423 424 def need_rd_protect(self, new_key_purpose): 425 for key in self.KEY_PURPOSES: 426 if key[0] == new_key_purpose: 427 return key[4] == "need_rd_protect" 428 429 def get(self, from_read=True): 430 for p in self.KEY_PURPOSES: 431 if p[1] == self.get_raw(from_read): 432 return p[0] 433 return "FORBIDDEN_STATE" 434 435 def get_name(self, raw_val): 436 for key in self.KEY_PURPOSES: 437 if key[1] == raw_val: 438 return key[0] 439 440 def save(self, new_value): 441 raw_val = int(self.check_format(str(new_value))) 442 return super(EfuseKeyPurposeField, self).save(raw_val) 443