1# This file describes eFuses for ESP32-H2 chip 2# 3# SPDX-FileCopyrightText: 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-H2": 78 raise esptool.FatalError( 79 "Expected the 'esp' param for ESP32-H2 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_MINOR"].get() == 2: 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 # Based on `CONFIG_SOC_XTAL_SUPPORT_32M=y` for this target from ESP-IDF configuration 239 if apb_freq != 32: 240 raise esptool.FatalError( 241 "The eFuse supports only xtal=32M (xtal was %d)" % apb_freq 242 ) 243 244 self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF) 245 self.update_reg( 246 self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28 247 ) 248 self.update_reg( 249 self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000 250 ) 251 self.update_reg( 252 self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190 253 ) 254 255 def get_coding_scheme_warnings(self, silent=False): 256 """Check if the coding scheme has detected any errors.""" 257 old_addr_reg = 0 258 reg_value = 0 259 ret_fail = False 260 for block in self.blocks: 261 if block.id == 0: 262 words = [ 263 self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) 264 for offs in range(5) 265 ] 266 block.err_bitarray.pos = 0 267 for word in reversed(words): 268 block.err_bitarray.overwrite(BitArray("uint:32=%d" % word)) 269 block.num_errors = block.err_bitarray.count(True) 270 block.fail = block.num_errors != 0 271 else: 272 addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[ 273 block.id 274 ] 275 if err_num_mask is None or err_num_offs is None or fail_bit is None: 276 continue 277 if addr_reg != old_addr_reg: 278 old_addr_reg = addr_reg 279 reg_value = self.read_reg(addr_reg) 280 block.fail = reg_value & (1 << fail_bit) != 0 281 block.num_errors = (reg_value >> err_num_offs) & err_num_mask 282 ret_fail |= block.fail 283 if not silent and (block.fail or block.num_errors): 284 print( 285 "Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]" 286 % (block.id, block.num_errors, block.fail) 287 ) 288 if (self.debug or ret_fail) and not silent: 289 self.print_status_regs() 290 return ret_fail 291 292 def summary(self): 293 # TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)" 294 return "" 295 296 297class EfuseField(base_fields.EfuseFieldBase): 298 @staticmethod 299 def convert(parent, efuse): 300 return { 301 "mac": EfuseMacField, 302 "keypurpose": EfuseKeyPurposeField, 303 "t_sensor": EfuseTempSensor, 304 "adc_tp": EfuseAdcPointCalibration, 305 "wafer": EfuseWafer, 306 }.get(efuse.class_type, EfuseField)(parent, efuse) 307 308 309class EfuseWafer(EfuseField): 310 def get(self, from_read=True): 311 hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read) 312 assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1 313 lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read) 314 assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3 315 return (hi_bits << 3) + lo_bits 316 317 def save(self, new_value): 318 raise esptool.FatalError("Burning %s is not supported" % self.name) 319 320 321class EfuseTempSensor(EfuseField): 322 def get(self, from_read=True): 323 value = self.get_bitstring(from_read) 324 sig = -1 if value[0] else 1 325 return sig * value[1:].uint * 0.1 326 327 328class EfuseAdcPointCalibration(EfuseField): 329 def get(self, from_read=True): 330 STEP_SIZE = 4 331 value = self.get_bitstring(from_read) 332 sig = -1 if value[0] else 1 333 return sig * value[1:].uint * STEP_SIZE 334 335 336class EfuseMacField(EfuseField): 337 def check_format(self, new_value_str): 338 if new_value_str is None: 339 raise esptool.FatalError( 340 "Required MAC Address in AA:CD:EF:01:02:03 format!" 341 ) 342 num_bytes = 8 if self.name == "MAC_EUI64" else 6 343 if new_value_str.count(":") != num_bytes - 1: 344 raise esptool.FatalError( 345 f"MAC Address needs to be a {num_bytes}-byte hexadecimal format " 346 "separated by colons (:)!" 347 ) 348 hexad = new_value_str.replace(":", "") 349 hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad 350 if len(hexad) != num_bytes * 2: 351 raise esptool.FatalError( 352 f"MAC Address needs to be a {num_bytes}-byte hexadecimal number " 353 f"({num_bytes * 2} hexadecimal characters)!" 354 ) 355 # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03', 356 bindata = binascii.unhexlify(hexad) 357 358 if not self.is_field_calculated(): 359 # unicast address check according to 360 # https://tools.ietf.org/html/rfc7042#section-2.1 361 if esptool.util.byte(bindata, 0) & 0x01: 362 raise esptool.FatalError("Custom MAC must be a unicast MAC!") 363 return bindata 364 365 def check(self): 366 errs, fail = self.parent.get_block_errors(self.block) 367 if errs != 0 or fail: 368 output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail) 369 else: 370 output = "OK" 371 return "(" + output + ")" 372 373 def get(self, from_read=True): 374 if self.name == "CUSTOM_MAC": 375 mac = self.get_raw(from_read)[::-1] 376 elif self.name == "MAC": 377 mac = self.get_raw(from_read) 378 elif self.name == "MAC_EUI64": 379 mac = self.parent["MAC"].get_bitstring(from_read).copy() 380 mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read) 381 mac.insert(mac_ext, 24) 382 mac = mac.bytes 383 else: 384 mac = self.get_raw(from_read) 385 return "%s %s" % (util.hexify(mac, ":"), self.check()) 386 387 def save(self, new_value): 388 def print_field(e, new_value): 389 print( 390 " - '{}' ({}) {} -> {}".format( 391 e.name, e.description, e.get_bitstring(), new_value 392 ) 393 ) 394 395 if self.name == "CUSTOM_MAC": 396 bitarray_mac = self.convert_to_bitstring(new_value) 397 print_field(self, bitarray_mac) 398 super(EfuseMacField, self).save(new_value) 399 else: 400 # Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible, 401 # as it's written in the factory. 402 raise esptool.FatalError(f"Burning {self.name} is not supported") 403 404 405# fmt: off 406class EfuseKeyPurposeField(EfuseField): 407 KEY_PURPOSES = [ 408 ("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use) 409 ("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key 410 ("RESERVED", 2, None, None, "no_need_rd_protect"), # Reserved 411 ("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption) 412 ("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode 413 ("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode) 414 ("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode) 415 ("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode 416 ("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest) 417 ("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest) 418 ("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest) 419 ] 420# fmt: on 421 KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES] 422 DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"] 423 424 def check_format(self, new_value_str): 425 # str convert to int: "XTS_AES_128_KEY" - > str(4) 426 # if int: 4 -> str(4) 427 raw_val = new_value_str 428 for purpose_name in self.KEY_PURPOSES: 429 if purpose_name[0] == new_value_str: 430 raw_val = str(purpose_name[1]) 431 break 432 if raw_val.isdigit(): 433 if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]: 434 raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val) 435 else: 436 raise esptool.FatalError("'%s' unknown name" % raw_val) 437 return raw_val 438 439 def need_reverse(self, new_key_purpose): 440 for key in self.KEY_PURPOSES: 441 if key[0] == new_key_purpose: 442 return key[3] == "Reverse" 443 444 def need_rd_protect(self, new_key_purpose): 445 for key in self.KEY_PURPOSES: 446 if key[0] == new_key_purpose: 447 return key[4] == "need_rd_protect" 448 449 def get(self, from_read=True): 450 for p in self.KEY_PURPOSES: 451 if p[1] == self.get_raw(from_read): 452 return p[0] 453 return "FORBIDDEN_STATE" 454 455 def get_name(self, raw_val): 456 for key in self.KEY_PURPOSES: 457 if key[1] == raw_val: 458 return key[0] 459 460 def save(self, new_value): 461 raw_val = int(self.check_format(str(new_value))) 462 str_new_value = self.get_name(raw_val) 463 if self.name == "KEY_PURPOSE_5" and str_new_value in ["XTS_AES_128_KEY", "ECDSA_KEY"]: 464 raise esptool.FatalError(f"{self.name} can not have {str_new_value} key due to a hardware bug (please see TRM for more details)") 465 return super(EfuseKeyPurposeField, self).save(raw_val) 466