Skip to content

Commit

Permalink
feat(espefuse): Postpone some efuses to burn them at the very end
Browse files Browse the repository at this point in the history
  • Loading branch information
KonstantinKondrashov authored and radimkarnis committed Jan 22, 2024
1 parent d448851 commit bdeec68
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 4 deletions.
9 changes: 9 additions & 0 deletions espefuse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ def main(custom_commandline=None, esp=None):
"Use with caution.",
action="store_true",
)
init_parser.add_argument(
"--postpone",
help="Postpone burning some efuses from BLOCK0 at the end, "
"(efuses which disable access to blocks or chip).",
action="store_true",
)

common_args, remaining_args = init_parser.parse_known_args(custom_commandline)
debug_mode = common_args.debug or ("dump" in remaining_args)
Expand Down Expand Up @@ -263,6 +269,8 @@ def main(custom_commandline=None, esp=None):
if there_are_multiple_burn_commands_in_args:
efuses.batch_mode_cnt += 1

efuses.postpone = common_args.postpone

try:
for rem_args in grouped_remaining_args:
args, unused_args = parser.parse_known_args(rem_args, namespace=common_args)
Expand All @@ -289,6 +297,7 @@ def main(custom_commandline=None, esp=None):
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
raise esptool.FatalError("BURN was not done")
print("Successful")
finally:
if not external_esp and not common_args.virt and esp._port:
esp._port.close()
Expand Down
106 changes: 102 additions & 4 deletions espefuse/efuse/base_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ class EspEfusesBase(object):
coding_scheme = None
force_write_always = None
batch_mode_cnt = 0
postpone = False

def __iter__(self):
return self.efuses.__iter__()
Expand Down Expand Up @@ -489,6 +490,59 @@ def update_efuses(self):
for efuse in self.efuses:
efuse.update(self.blocks[efuse.block].bitarray)

def postpone_efuses_from_block0_to_burn(self, block):
postpone_efuses = {}

if block.id != 0:
return postpone_efuses

# We need to check this list of efuses. If we are going to burn an efuse
# from this list, then we need to split the burn operation into two
# steps. The first step involves burning efuses not in this list. In
# case of an error during this step, we can recover by burning the
# efuses from this list at the very end. This approach provides the
# ability to recover efuses if an error occurs during the initial burn
# operation.

# List the efuses here that must be burned at the very end, such as read
# and write protection fields, as well as efuses that disable
# communication with the espefuse tool.
efuses_list = ["WR_DIS", "RD_DIS"]
if self._esp.CHIP_NAME == "ESP32":
# Efuses below disables communication with the espefuse tool.
efuses_list.append("UART_DOWNLOAD_DIS")
# other efuses that are better to burn at the very end.
efuses_list.append("ABS_DONE_1")
efuses_list.append("FLASH_CRYPT_CNT")
else:
# Efuses below disables communication with the espefuse tool.
efuses_list.append("ENABLE_SECURITY_DOWNLOAD")
efuses_list.append("DIS_DOWNLOAD_MODE")
# other efuses that are better to burn at the very end.
efuses_list.append("SPI_BOOT_CRYPT_CNT")
efuses_list.append("SECURE_BOOT_EN")

def get_raw_value_from_write(self, efuse_name):
return self[efuse_name].get_bitstring(from_read=False)

for efuse_name in efuses_list:
postpone_efuses[efuse_name] = get_raw_value_from_write(self, efuse_name)

if any(value != 0 for value in postpone_efuses.values()):
if self.debug:
print("These BLOCK0 efuses will be burned later at the very end:")
print(postpone_efuses)
# exclude these efuses from the first burn (postpone them till the end).
for key_name in postpone_efuses.keys():
self[key_name].reset()
return postpone_efuses

def recover_postponed_efuses_from_block0_to_burn(self, postpone_efuses):
if any(value != 0 for value in postpone_efuses.values()):
print("Burn postponed efuses from BLOCK0.")
for key_name in postpone_efuses.keys():
self[key_name].save(postpone_efuses[key_name])

def burn_all(self, check_batch_mode=False):
if check_batch_mode:
if self.batch_mode_cnt != 0:
Expand All @@ -508,18 +562,44 @@ def burn_all(self, check_batch_mode=False):
have_wr_data_for_burn = True
if not have_wr_data_for_burn:
print("Nothing to burn, see messages above.")
return
return True
EspEfusesBase.confirm("", self.do_not_confirm)

# Burn from BLKn -> BLK0. Because BLK0 can set rd or/and wr protection bits.
for block in reversed(self.blocks):
def burn_block(block, postponed_efuses):
old_fail = block.fail
old_num_errors = block.num_errors
block.burn()
if (block.fail and old_fail != block.fail) or (
block.num_errors and block.num_errors > old_num_errors
):
if postponed_efuses:
print("The postponed efuses were not burned due to an error.")
print("\t1. Try to fix a coding error by this cmd:")
print("\t 'espefuse.py check_error --recovery'")
command_string = " ".join(
f"{key} {value}"
for key, value in postponed_efuses.items()
if value.any(True)
)
print("\t2. Then run the cmd to burn all postponed efuses:")
print(f"\t 'espefuse.py burn_efuse {command_string}'")

raise esptool.FatalError("Error(s) were detected in eFuses")

# Burn from BLKn -> BLK0. Because BLK0 can set rd or/and wr protection bits.
for block in reversed(self.blocks):
postponed_efuses = (
self.postpone_efuses_from_block0_to_burn(block)
if self.postpone
else None
)

burn_block(block, postponed_efuses)

if postponed_efuses:
self.recover_postponed_efuses_from_block0_to_burn(postponed_efuses)
burn_block(block, postponed_efuses)

print("Reading updated efuses...")
self.read_coding_scheme()
self.read_blocks()
Expand Down Expand Up @@ -628,14 +708,21 @@ def convert_to_bitstring(self, new_value):

def check_new_value(self, bitarray_new_value):
bitarray_old_value = self.get_bitstring() | self.get_bitstring(from_read=False)

if not bitarray_new_value.any(True) and not bitarray_old_value.any(True):
return

if bitarray_new_value.len != bitarray_old_value.len:
raise esptool.FatalError(
"For {} efuse, the length of the new value is wrong, "
"expected {} bits, was {} bits.".format(
self.name, bitarray_old_value.len, bitarray_new_value.len
)
)
if bitarray_new_value == bitarray_old_value:
if (
bitarray_new_value == bitarray_old_value
or bitarray_new_value & self.get_bitstring() == bitarray_new_value
):
error_msg = "\tThe same value for {} ".format(self.name)
error_msg += "is already burned. Do not change the efuse."
print(error_msg)
Expand Down Expand Up @@ -752,3 +839,14 @@ def get_info(self):
if name is not None:
output += f"\n Purpose: {self.parent[name].get()}\n "
return output

def reset(self):
# resets a efuse that is prepared for burning
bitarray_field = self.convert_to_bitstring(0)
block = self.parent.blocks[self.block]
wr_bitarray_temp = block.wr_bitarray.copy()
position = wr_bitarray_temp.length - (
self.word * 32 + self.pos + bitarray_field.len
)
wr_bitarray_temp.overwrite(bitarray_field, pos=position)
block.wr_bitarray = wr_bitarray_temp
26 changes: 26 additions & 0 deletions test/test_espefuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -1977,3 +1977,29 @@ def test_burn_ecdsa_key(self):
"key due to a hardware bug (please see TRM for more details)",
ret_code=2,
)


class TestPostponedEfuses(EfuseTestCase):
def test_postpone_efuses(self):
if arg_chip == "esp32":
cmd = f"--postpone \
burn_efuse UART_DOWNLOAD_DIS 1 \
burn_key BLOCK1 {IMAGES_DIR}/256bit \
burn_efuse ABS_DONE_1 1 FLASH_CRYPT_CNT 1"
num = 1
else:
sb_digest_name = (
"SECURE_BOOT_DIGEST" if arg_chip == "esp32c2" else "SECURE_BOOT_DIGEST0"
)
cmd = f"--postpone \
burn_efuse ENABLE_SECURITY_DOWNLOAD 1 DIS_DOWNLOAD_MODE 1 \
SECURE_VERSION 1 \
burn_key BLOCK_KEY0 {IMAGES_DIR}/256bit {sb_digest_name} \
burn_efuse SPI_BOOT_CRYPT_CNT 1 SECURE_BOOT_EN 1"
num = 3 if arg_chip == "esp32c2" else 4
output = self.espefuse_py(cmd)
assert f"BURN BLOCK{num} - OK" in output
assert "BURN BLOCK0 - OK" in output
assert "Burn postponed efuses from BLOCK0" in output
assert "BURN BLOCK0 - OK" in output
assert "Successful" in output

0 comments on commit bdeec68

Please sign in to comment.