1# This file describes eFuses for ESP32-C2 chip 2# 3# SPDX-FileCopyrightText: 2021-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-C2": 71 raise esptool.FatalError( 72 "Expected the 'esp' param for ESP32-C2 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_MINOR"].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 116 def __getitem__(self, efuse_name): 117 """Return the efuse field with the given name""" 118 for e in self.efuses: 119 if efuse_name == e.name: 120 return e 121 new_fields = False 122 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: 123 e = self.Fields.get(efuse) 124 if e.name == efuse_name: 125 self.efuses += [ 126 EfuseField.from_tuple( 127 self, self.Fields.get(efuse), self.Fields.get(efuse).class_type 128 ) 129 for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES 130 ] 131 new_fields = True 132 if new_fields: 133 for e in self.efuses: 134 if efuse_name == e.name: 135 return e 136 raise KeyError 137 138 def read_coding_scheme(self): 139 self.coding_scheme = self.REGS.CODING_SCHEME_RS 140 141 def print_status_regs(self): 142 print("") 143 self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True) 144 print( 145 "{:27} 0x{:08x}".format( 146 "EFUSE_RD_RS_ERR_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR_REG) 147 ) 148 ) 149 150 def get_block_errors(self, block_num): 151 """Returns (error count, failure boolean flag)""" 152 return self.blocks[block_num].num_errors, self.blocks[block_num].fail 153 154 def efuse_controller_setup(self): 155 self.set_efuse_timing() 156 self.clear_pgm_registers() 157 self.wait_efuse_idle() 158 159 def write_efuses(self, block): 160 self.efuse_program(block) 161 return self.get_coding_scheme_warnings(silent=True) 162 163 def clear_pgm_registers(self): 164 self.wait_efuse_idle() 165 for r in range( 166 self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4 167 ): 168 self.write_reg(r, 0) 169 170 def wait_efuse_idle(self): 171 deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT 172 while time.time() < deadline: 173 # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0: 174 if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: 175 return 176 raise esptool.FatalError( 177 "Timed out waiting for Efuse controller command to complete" 178 ) 179 180 def efuse_program(self, block): 181 self.wait_efuse_idle() 182 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE) 183 self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2)) 184 self.wait_efuse_idle() 185 self.clear_pgm_registers() 186 self.efuse_read() 187 188 def efuse_read(self): 189 self.wait_efuse_idle() 190 self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE) 191 # need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some 192 # efuse registers after each command is completed 193 # if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip. 194 try: 195 self.write_reg( 196 self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 197 ) 198 self.wait_efuse_idle() 199 except esptool.FatalError: 200 secure_download_mode_before = self._esp.secure_download_mode 201 202 try: 203 self._esp = self.reconnect_chip(self._esp) 204 except esptool.FatalError: 205 print("Can not re-connect to the chip") 206 if not self["DIS_DOWNLOAD_MODE"].get() and self[ 207 "DIS_DOWNLOAD_MODE" 208 ].get(from_read=False): 209 print( 210 "This is the correct behavior as we are actually burning " 211 "DIS_DOWNLOAD_MODE which disables the connection to the chip" 212 ) 213 print("DIS_DOWNLOAD_MODE is enabled") 214 print("Successful") 215 exit(0) # finish without errors 216 raise 217 218 print("Established a connection with the chip") 219 if self._esp.secure_download_mode and not secure_download_mode_before: 220 print("Secure download mode is enabled") 221 if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[ 222 "ENABLE_SECURITY_DOWNLOAD" 223 ].get(from_read=False): 224 print( 225 "espefuse tool can not continue to work in Secure download mode" 226 ) 227 print("ENABLE_SECURITY_DOWNLOAD is enabled") 228 print("Successful") 229 exit(0) # finish without errors 230 raise 231 232 def set_efuse_timing(self): 233 """Set timing registers for burning efuses""" 234 # Configure clock 235 xtal_freq = self.get_crystal_freq() 236 if xtal_freq not in [26, 40]: 237 raise esptool.FatalError( 238 "The eFuse supports only xtal=26M and 40M (xtal was %d)" % xtal_freq 239 ) 240 241 self.update_reg( 242 self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190 243 ) 244 245 tpgm_inactive_val = 200 if xtal_freq == 40 else 130 246 self.update_reg( 247 self.REGS.EFUSE_WR_TIM_CONF0_REG, 248 self.REGS.EFUSE_TPGM_INACTIVE_M, 249 tpgm_inactive_val, 250 ) 251 252 def get_coding_scheme_warnings(self, silent=False): 253 """Check if the coding scheme has detected any errors.""" 254 old_addr_reg = 0 255 reg_value = 0 256 ret_fail = False 257 for block in self.blocks: 258 if block.id == 0: 259 words = [ 260 self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR_REG + offs * 4) 261 for offs in range(1) 262 ] 263 data = BitArray() 264 for word in reversed(words): 265 data.append("uint:32=%d" % word) 266 # pos=32 because EFUSE_WR_DIS goes first it is 32bit long 267 # and not under error control 268 block.err_bitarray.overwrite(data, pos=32) 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 from_tuple(parent, efuse_tuple, type_class): 300 return { 301 "mac": EfuseMacField, 302 "keypurpose": EfuseKeyPurposeField, 303 "t_sensor": EfuseTempSensor, 304 "adc_tp": EfuseAdcPointCalibration, 305 }.get(type_class, EfuseField)(parent, efuse_tuple) 306 307 def get_info(self): 308 output = "%s (BLOCK%d)" % (self.name, self.block) 309 errs, fail = self.parent.get_block_errors(self.block) 310 if errs != 0 or fail: 311 output += ( 312 "[FAIL:%d]" % (fail) 313 if self.block == 0 314 else "[ERRS:%d FAIL:%d]" % (errs, fail) 315 ) 316 if self.efuse_class == "keyblock": 317 name = self.parent.blocks[self.block].key_purpose_name 318 if name is not None: 319 output += "\n Purpose: %s\n " % (self.parent[name].get()) 320 return output 321 322 323class EfuseTempSensor(EfuseField): 324 def get(self, from_read=True): 325 value = self.get_bitstring(from_read) 326 sig = -1 if value[0] else 1 327 return sig * value[1:].uint * 0.1 328 329 330class EfuseAdcPointCalibration(EfuseField): 331 def get(self, from_read=True): 332 STEP_SIZE = 4 333 value = self.get_bitstring(from_read) 334 sig = -1 if value[0] else 1 335 return sig * value[1:].uint * STEP_SIZE 336 337 338class EfuseMacField(EfuseField): 339 def check_format(self, new_value_str): 340 if new_value_str is None: 341 raise esptool.FatalError( 342 "Required MAC Address in AA:CD:EF:01:02:03 format!" 343 ) 344 if new_value_str.count(":") != 5: 345 raise esptool.FatalError( 346 "MAC Address needs to be a 6-byte hexadecimal format " 347 "separated by colons (:)!" 348 ) 349 hexad = new_value_str.replace(":", "") 350 if len(hexad) != 12: 351 raise esptool.FatalError( 352 "MAC Address needs to be a 6-byte hexadecimal number " 353 "(12 hexadecimal characters)!" 354 ) 355 # order of bytearray = b'\xaa\xcd\xef\x01\x02\x03', 356 bindata = binascii.unhexlify(hexad) 357 # unicast address check according to 358 # https://tools.ietf.org/html/rfc7042#section-2.1 359 if esptool.util.byte(bindata, 0) & 0x01: 360 raise esptool.FatalError("Custom MAC must be a unicast MAC!") 361 return bindata 362 363 def check(self): 364 errs, fail = self.parent.get_block_errors(self.block) 365 if errs != 0 or fail: 366 output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail) 367 else: 368 output = "OK" 369 return "(" + output + ")" 370 371 def get(self, from_read=True): 372 if self.name == "CUSTOM_MAC": 373 mac = self.get_raw(from_read)[::-1] 374 else: 375 mac = self.get_raw(from_read) 376 return "%s %s" % (util.hexify(mac, ":"), self.check()) 377 378 def save(self, new_value): 379 def print_field(e, new_value): 380 print( 381 " - '{}' ({}) {} -> {}".format( 382 e.name, e.description, e.get_bitstring(), new_value 383 ) 384 ) 385 386 if self.name == "CUSTOM_MAC": 387 bitarray_mac = self.convert_to_bitstring(new_value) 388 print_field(self, bitarray_mac) 389 super(EfuseMacField, self).save(new_value) 390 else: 391 raise esptool.FatalError("Writing Factory MAC address is not supported") 392 393 394class EfuseKeyPurposeField(EfuseField): 395 KEY_PURPOSES = [ 396 ("USER", 0, None), # User purposes (software-only use) 397 ( 398 "XTS_AES_128_KEY", 399 1, 400 None, 401 ), # (whole 256bits) XTS_AES_128_KEY (flash/PSRAM encryption) 402 ( 403 "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS", 404 2, 405 None, 406 ), # (lo 128bits) XTS_AES_128_KEY (flash/PSRAM encryption) 407 ( 408 "SECURE_BOOT_DIGEST", 409 3, 410 "DIGEST", 411 ), # (hi 128bits)SECURE_BOOT_DIGEST (Secure Boot key digest) 412 ] 413 414 KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES] 415 DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"] 416