This commit is contained in:
Jonas Zeunert
2024-08-16 21:57:55 +02:00
parent adeb5c5ec7
commit 4309a2d185
1696 changed files with 279655 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
# SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os
import sys
from collections import namedtuple
from io import StringIO
import espefuse.efuse.esp32 as esp32_efuse
import espefuse.efuse.esp32c2 as esp32c2_efuse
import espefuse.efuse.esp32c3 as esp32c3_efuse
import espefuse.efuse.esp32c6 as esp32c6_efuse
import espefuse.efuse.esp32h2 as esp32h2_efuse
import espefuse.efuse.esp32h2beta1 as esp32h2beta1_efuse
import espefuse.efuse.esp32p4 as esp32p4_efuse
import espefuse.efuse.esp32s2 as esp32s2_efuse
import espefuse.efuse.esp32s3 as esp32s3_efuse
import espefuse.efuse.esp32s3beta2 as esp32s3beta2_efuse
import esptool
DefChip = namedtuple("DefChip", ["chip_name", "efuse_lib", "chip_class"])
SUPPORTED_BURN_COMMANDS = [
"read_protect_efuse",
"write_protect_efuse",
"burn_efuse",
"burn_block_data",
"burn_bit",
"burn_key",
"burn_key_digest",
"burn_custom_mac",
"set_flash_voltage",
"execute_scripts",
]
SUPPORTED_COMMANDS = [
"summary",
"dump",
"get_custom_mac",
"adc_info",
"check_error",
] + SUPPORTED_BURN_COMMANDS
SUPPORTED_CHIPS = {
"esp32": DefChip("ESP32", esp32_efuse, esptool.targets.ESP32ROM),
"esp32c2": DefChip("ESP32-C2", esp32c2_efuse, esptool.targets.ESP32C2ROM),
"esp32c3": DefChip("ESP32-C3", esp32c3_efuse, esptool.targets.ESP32C3ROM),
"esp32c6": DefChip("ESP32-C6", esp32c6_efuse, esptool.targets.ESP32C6ROM),
"esp32h2": DefChip("ESP32-H2", esp32h2_efuse, esptool.targets.ESP32H2ROM),
"esp32p4": DefChip("ESP32-P4", esp32p4_efuse, esptool.targets.ESP32P4ROM),
"esp32h2beta1": DefChip(
"ESP32-H2(beta1)", esp32h2beta1_efuse, esptool.targets.ESP32H2BETA1ROM
),
"esp32s2": DefChip("ESP32-S2", esp32s2_efuse, esptool.targets.ESP32S2ROM),
"esp32s3": DefChip("ESP32-S3", esp32s3_efuse, esptool.targets.ESP32S3ROM),
"esp32s3beta2": DefChip(
"ESP32-S3(beta2)", esp32s3beta2_efuse, esptool.targets.ESP32S3BETA2ROM
),
}
def get_esp(
port,
baud,
connect_mode,
chip="auto",
skip_connect=False,
virt=False,
debug=False,
virt_efuse_file=None,
):
if chip not in ["auto"] + list(SUPPORTED_CHIPS.keys()):
raise esptool.FatalError("get_esp: Unsupported chip (%s)" % chip)
if virt:
efuse = SUPPORTED_CHIPS.get(chip, SUPPORTED_CHIPS["esp32"]).efuse_lib
esp = efuse.EmulateEfuseController(virt_efuse_file, debug)
else:
if chip == "auto" and not skip_connect:
esp = esptool.cmds.detect_chip(port, baud, connect_mode)
else:
esp = SUPPORTED_CHIPS.get(chip, SUPPORTED_CHIPS["esp32"]).chip_class(
port if not skip_connect else StringIO(), baud
)
if not skip_connect:
esp.connect(connect_mode)
return esp
def get_efuses(esp, skip_connect=False, debug_mode=False, do_not_confirm=False):
for name in SUPPORTED_CHIPS:
if SUPPORTED_CHIPS[name].chip_name == esp.CHIP_NAME:
efuse = SUPPORTED_CHIPS[name].efuse_lib
return (
efuse.EspEfuses(esp, skip_connect, debug_mode, do_not_confirm),
efuse.operations,
)
else:
raise esptool.FatalError("get_efuses: Unsupported chip (%s)" % esp.CHIP_NAME)
def split_on_groups(all_args):
"""
This function splits the all_args list into groups,
where each item is a cmd with all its args.
Example:
all_args:
['burn_key_digest', 'secure_images/ecdsa256_secure_boot_signing_key_v2.pem',
'burn_key', 'BLOCK_KEY0', 'images/efuse/128bit_key',
'XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS']
used_cmds: ['burn_key_digest', 'burn_key']
groups:
[['burn_key_digest', 'secure_images/ecdsa256_secure_boot_signing_key_v2.pem'],
['burn_key', 'BLOCK_KEY0', 'images/efuse/128bit_key',
'XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS']]
"""
groups = []
cmd = []
used_cmds = []
for item in all_args:
if item in SUPPORTED_COMMANDS:
used_cmds.append(item)
if cmd != []:
groups.append(cmd)
cmd = []
cmd.append(item)
if cmd:
groups.append(cmd)
return groups, used_cmds
def main(custom_commandline=None, esp=None):
"""
Main function for espefuse
custom_commandline - Optional override for default arguments parsing
(that uses sys.argv), can be a list of custom arguments as strings.
Arguments and their values need to be added as individual items to the list
e.g. "--port /dev/ttyUSB1" thus becomes ['--port', '/dev/ttyUSB1'].
esp - Optional override of the connected device previously
returned by esptool.get_default_connected_device()
"""
external_esp = esp is not None
init_parser = argparse.ArgumentParser(
description="espefuse.py v%s - [ESP32xx] efuse get/set tool"
% esptool.__version__,
prog="espefuse",
add_help=False,
)
init_parser.add_argument(
"--chip",
"-c",
help="Target chip type",
choices=["auto"] + list(SUPPORTED_CHIPS.keys()),
default=os.environ.get("ESPTOOL_CHIP", "auto"),
)
init_parser.add_argument(
"--baud",
"-b",
help="Serial port baud rate used when flashing/reading",
type=esptool.arg_auto_int,
default=os.environ.get("ESPTOOL_BAUD", esptool.loader.ESPLoader.ESP_ROM_BAUD),
)
init_parser.add_argument(
"--port",
"-p",
help="Serial port device",
default=os.environ.get("ESPTOOL_PORT", esptool.loader.ESPLoader.DEFAULT_PORT),
)
init_parser.add_argument(
"--before",
help="What to do before connecting to the chip",
choices=["default_reset", "usb_reset", "no_reset", "no_reset_no_sync"],
default="default_reset",
)
init_parser.add_argument(
"--debug",
"-d",
help="Show debugging information (loglevel=DEBUG)",
action="store_true",
)
init_parser.add_argument(
"--virt",
help="For host tests, the tool will work in the virtual mode "
"(without connecting to a chip).",
action="store_true",
)
init_parser.add_argument(
"--path-efuse-file",
help="For host tests, saves efuse memory to file.",
type=str,
default=None,
)
init_parser.add_argument(
"--do-not-confirm",
help="Do not pause for confirmation before permanently writing efuses. "
"Use with caution.",
action="store_true",
)
common_args, remaining_args = init_parser.parse_known_args(custom_commandline)
debug_mode = common_args.debug or ("dump" in remaining_args)
just_print_help = [
True for arg in remaining_args if arg in ["--help", "-h"]
] or remaining_args == []
print("espefuse.py v{}".format(esptool.__version__))
if not external_esp:
try:
esp = get_esp(
common_args.port,
common_args.baud,
common_args.before,
common_args.chip,
just_print_help,
common_args.virt,
common_args.debug,
common_args.path_efuse_file,
)
except esptool.FatalError as e:
raise esptool.FatalError(
f"{e}\nPlease make sure that you have specified "
"the right port with the --port argument"
)
# TODO: Require the --port argument in the next major release, ESPTOOL-490
efuses, efuse_operations = get_efuses(
esp, just_print_help, debug_mode, common_args.do_not_confirm
)
parser = argparse.ArgumentParser(parents=[init_parser])
subparsers = parser.add_subparsers(
dest="operation", help="Run espefuse.py {command} -h for additional help"
)
efuse_operations.add_commands(subparsers, efuses)
grouped_remaining_args, used_cmds = split_on_groups(remaining_args)
if len(grouped_remaining_args) == 0:
parser.print_help()
parser.exit(1)
there_are_multiple_burn_commands_in_args = (
sum(cmd in SUPPORTED_BURN_COMMANDS for cmd in used_cmds) > 1
)
if there_are_multiple_burn_commands_in_args:
efuses.batch_mode_cnt += 1
try:
for rem_args in grouped_remaining_args:
args, unused_args = parser.parse_known_args(rem_args, namespace=common_args)
if args.operation is None:
parser.print_help()
parser.exit(1)
assert (
len(unused_args) == 0
), 'Not all commands were recognized "{}"'.format(unused_args)
operation_func = vars(efuse_operations)[args.operation]
# each 'operation' is a module-level function of the same name
print('\n=== Run "{}" command ==='.format(args.operation))
if hasattr(args, "show_sensitive_info"):
if args.show_sensitive_info or args.debug:
args.show_sensitive_info = True
else:
print("Sensitive data will be hidden (see --show-sensitive-info)")
operation_func(esp, efuses, args)
if there_are_multiple_burn_commands_in_args:
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
raise esptool.FatalError("BURN was not done")
finally:
if not external_esp and not common_args.virt and esp._port:
esp._port.close()
def _main():
try:
main()
except esptool.FatalError as e:
print("\nA fatal error occurred: %s" % e)
sys.exit(2)
if __name__ == "__main__":
_main()

View File

@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import espefuse
if __name__ == "__main__":
espefuse._main()

View File

@@ -0,0 +1,754 @@
# This file describes the common eFuses structures for chips
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import sys
from bitstring import BitArray, BitStream, CreationError
import esptool
from . import util
class CheckArgValue(object):
def __init__(self, efuses, name):
self.efuses = efuses
self.name = name
def __call__(self, new_value_str):
def check_arg_value(efuse, new_value):
if efuse.efuse_type.startswith("bool"):
new_value = 1 if new_value is None else int(new_value, 0)
if new_value != 1:
raise esptool.FatalError(
"New value is not accepted for efuse '{}' "
"(will always burn 0->1), given value={}".format(
efuse.name, new_value
)
)
elif efuse.efuse_type.startswith(("int", "uint")):
if efuse.efuse_class == "bitcount":
if new_value is None:
# find the first unset bit and set it
old_value = efuse.get_raw()
new_value = old_value
bit = 1
while new_value == old_value:
new_value = bit | old_value
bit <<= 1
else:
new_value = int(new_value, 0)
else:
if new_value is None:
raise esptool.FatalError(
"New value required for efuse '{}' (given None)".format(
efuse.name
)
)
new_value = int(new_value, 0)
if new_value == 0:
raise esptool.FatalError(
"New value should not be 0 for '{}' "
"(given value= {})".format(efuse.name, new_value)
)
elif efuse.efuse_type.startswith("bytes"):
if new_value is None:
raise esptool.FatalError(
"New value required for efuse '{}' "
"(given None)".format(efuse.name)
)
if len(new_value) * 8 != efuse.bitarray.len:
raise esptool.FatalError(
"The length of efuse '{}' ({} bits) "
"(given len of the new value= {} bits)".format(
efuse.name, efuse.bitarray.len, len(new_value) * 8
)
)
else:
raise esptool.FatalError(
"The '{}' type for the '{}' efuse is not supported yet.".format(
efuse.efuse_type, efuse.name
)
)
return new_value
efuse = self.efuses[self.name]
new_value = efuse.check_format(new_value_str)
return check_arg_value(efuse, new_value)
class EfuseProtectBase(object):
# This class is used by EfuseBlockBase and EfuseFieldBase
def get_read_disable_mask(self, blk_part=None):
"""Returns mask of read protection bits
blk_part:
- None: Calculate mask for all read protection bits.
- a number: Calculate mask only for specific item in read protection list.
"""
mask = 0
if isinstance(self.read_disable_bit, list):
if blk_part is None:
for i in self.read_disable_bit:
mask |= 1 << i
else:
mask |= 1 << self.read_disable_bit[blk_part]
else:
mask = 1 << self.read_disable_bit
return mask
def get_count_read_disable_bits(self):
"""Returns the number of read protection bits used by the field"""
# On the C2 chip, BLOCK_KEY0 has two read protection bits [0, 1].
return bin(self.get_read_disable_mask()).count("1")
def is_readable(self, blk_part=None):
"""Return true if the efuse is readable by software"""
num_bit = self.read_disable_bit
if num_bit is None:
return True # read cannot be disabled
return (self.parent["RD_DIS"].get() & self.get_read_disable_mask(blk_part)) == 0
def disable_read(self):
num_bit = self.read_disable_bit
if num_bit is None:
raise esptool.FatalError("This efuse cannot be read-disabled")
if not self.parent["RD_DIS"].is_writeable():
raise esptool.FatalError(
"This efuse cannot be read-disabled due the to RD_DIS field is "
"already write-disabled"
)
self.parent["RD_DIS"].save(self.get_read_disable_mask())
def is_writeable(self):
num_bit = self.write_disable_bit
if num_bit is None:
return True # write cannot be disabled
return (self.parent["WR_DIS"].get() & (1 << num_bit)) == 0
def disable_write(self):
num_bit = self.write_disable_bit
if not self.parent["WR_DIS"].is_writeable():
raise esptool.FatalError(
"This efuse cannot be write-disabled due to the WR_DIS field is "
"already write-disabled"
)
self.parent["WR_DIS"].save(1 << num_bit)
def check_wr_rd_protect(self):
if not self.is_readable():
error_msg = "\t{} is read-protected.".format(self.name)
"The written value can not be read, the efuse/block looks as all 0.\n"
error_msg += "\tBurn in this case may damage an already written value."
self.parent.print_error_msg(error_msg)
if not self.is_writeable():
error_msg = "\t{} is write-protected. Burn is not possible.".format(
self.name
)
self.parent.print_error_msg(error_msg)
class EfuseBlockBase(EfuseProtectBase):
def __init__(self, parent, param, skip_read=False):
self.parent = parent
self.name = param.name
self.alias = param.alias
self.id = param.id
self.rd_addr = param.rd_addr
self.wr_addr = param.wr_addr
self.write_disable_bit = param.write_disable_bit
self.read_disable_bit = param.read_disable_bit
self.len = param.len
self.key_purpose_name = param.key_purpose
bit_block_len = self.get_block_len() * 8
self.bitarray = BitStream(bit_block_len)
self.bitarray.set(0)
self.wr_bitarray = BitStream(bit_block_len)
self.wr_bitarray.set(0)
self.fail = False
self.num_errors = 0
if self.id == 0:
self.err_bitarray = BitStream(bit_block_len)
self.err_bitarray.set(0)
else:
self.err_bitarray = None
if not skip_read:
self.read()
def get_block_len(self):
coding_scheme = self.get_coding_scheme()
if coding_scheme == self.parent.REGS.CODING_SCHEME_NONE:
return self.len * 4
elif coding_scheme == self.parent.REGS.CODING_SCHEME_34:
return (self.len * 3 // 4) * 4
elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS:
return self.len * 4
else:
raise esptool.FatalError(
"Coding scheme (%d) not supported" % (coding_scheme)
)
def get_coding_scheme(self):
if self.id == 0:
return self.parent.REGS.CODING_SCHEME_NONE
else:
return self.parent.coding_scheme
def get_raw(self, from_read=True):
if from_read:
return self.bitarray.bytes
else:
return self.wr_bitarray.bytes
def get(self, from_read=True):
self.get_bitstring(from_read=from_read)
def get_bitstring(self, from_read=True):
if from_read:
return self.bitarray
else:
return self.wr_bitarray
def convert_to_bitstring(self, new_data):
if isinstance(new_data, BitArray):
return new_data
else:
return BitArray(bytes=new_data, length=len(new_data) * 8)
def get_words(self):
def get_offsets(self):
return [x + self.rd_addr for x in range(0, self.get_block_len(), 4)]
return [self.parent.read_reg(offs) for offs in get_offsets(self)]
def read(self):
words = self.get_words()
data = BitArray()
for word in reversed(words):
data.append("uint:32=%d" % word)
self.bitarray.overwrite(data, pos=0)
self.print_block(self.bitarray, "read_regs")
def print_block(self, bit_string, comment, debug=False):
if self.parent.debug or debug:
bit_string.pos = 0
print(
"%-15s (%-16s) [%-2d] %s:"
% (self.name, " ".join(self.alias)[:16], self.id, comment),
" ".join(
[
"%08x" % word
for word in bit_string.readlist(
"%d*uint:32" % (bit_string.len / 32)
)[::-1]
]
),
)
def check_wr_data(self):
wr_data = self.wr_bitarray
if wr_data.all(False):
# nothing to burn
if self.parent.debug:
print("[{:02}] {:20} nothing to burn".format(self.id, self.name))
return False
if len(wr_data.bytes) != len(self.bitarray.bytes):
raise esptool.FatalError(
"Data does not fit: the block%d size is %d bytes, data is %d bytes"
% (self.id, len(self.bitarray.bytes), len(wr_data.bytes))
)
self.check_wr_rd_protect()
if self.get_bitstring().all(False):
print(
"[{:02}] {:20} is empty, will burn the new value".format(
self.id, self.name
)
)
else:
# the written block in chip is not empty
if self.get_bitstring() == wr_data:
print(
"[{:02}] {:20} is already written the same value, "
"continue with EMPTY_BLOCK".format(self.id, self.name)
)
wr_data.set(0)
else:
print("[{:02}] {:20} is not empty".format(self.id, self.name))
print("\t(written ):", self.get_bitstring())
print("\t(to write):", wr_data)
mask = self.get_bitstring() & wr_data
if mask == wr_data:
print(
"\tAll wr_data bits are set in the written block, "
"continue with EMPTY_BLOCK."
)
wr_data.set(0)
else:
coding_scheme = self.get_coding_scheme()
if coding_scheme == self.parent.REGS.CODING_SCHEME_NONE:
print("\t(coding scheme = NONE)")
elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS:
print("\t(coding scheme = RS)")
error_msg = (
"\tBurn into %s is forbidden "
"(RS coding scheme does not allow this)." % (self.name)
)
self.parent.print_error_msg(error_msg)
elif coding_scheme == self.parent.REGS.CODING_SCHEME_34:
print("\t(coding scheme = 3/4)")
data_can_not_be_burn = False
for i in range(0, self.get_bitstring().len, 6 * 8):
rd_chunk = self.get_bitstring()[i : i + 6 * 8 :]
wr_chunk = wr_data[i : i + 6 * 8 :]
if rd_chunk.any(True):
if wr_chunk.any(True):
print(
"\twritten chunk [%d] and wr_chunk "
"are not empty. " % (i // (6 * 8)),
end="",
)
if rd_chunk == wr_chunk:
print(
"wr_chunk == rd_chunk. "
"Countinue with empty chunk."
)
wr_data[i : i + 6 * 8 :].set(0)
else:
print("wr_chunk != rd_chunk. Can not burn.")
print("\twritten ", rd_chunk)
print("\tto write", wr_chunk)
data_can_not_be_burn = True
if data_can_not_be_burn:
error_msg = (
"\tBurn into %s is forbidden "
"(3/4 coding scheme does not allow this)." % (self.name)
)
self.parent.print_error_msg(error_msg)
else:
raise esptool.FatalError(
"The coding scheme ({}) is not supported".format(
coding_scheme
)
)
def save(self, new_data):
# new_data will be checked by check_wr_data() during burn_all()
# new_data (bytes) = [0][1][2] ... [N] (original data)
# in string format = [0] [1] [2] ... [N] (util.hexify(data, " "))
# in hex format = 0x[N]....[2][1][0] (from bitstring print(data))
# in reg format = [3][2][1][0] ... [N][][][] (as it will be in the device)
# in bitstring = [N] ... [2][1][0] (to get a correct bitstring
# need to reverse new_data)
# *[x] - means a byte.
data = BitStream(bytes=new_data[::-1], length=len(new_data) * 8)
if self.parent.debug:
print(
"\twritten : {} ->\n\tto write: {}".format(self.get_bitstring(), data)
)
self.wr_bitarray.overwrite(self.wr_bitarray | data, pos=0)
def burn_words(self, words):
for burns in range(3):
self.parent.efuse_controller_setup()
if self.parent.debug:
print("Write data to BLOCK%d" % (self.id))
write_reg_addr = self.wr_addr
for word in words:
# for ep32s2: using EFUSE_PGM_DATA[0..7]_REG for writing data
# 32 bytes to EFUSE_PGM_DATA[0..7]_REG
# 12 bytes to EFUSE_CHECK_VALUE[0..2]_REG. These regs are next after
# EFUSE_PGM_DATA_REG
# for esp32:
# each block has the special regs EFUSE_BLK[0..3]_WDATA[0..7]_REG
# for writing data
if self.parent.debug:
print("Addr 0x%08x, data=0x%08x" % (write_reg_addr, word))
self.parent.write_reg(write_reg_addr, word)
write_reg_addr += 4
self.parent.write_efuses(self.id)
for _ in range(5):
self.parent.efuse_read()
self.parent.get_coding_scheme_warnings(silent=True)
if self.fail or self.num_errors:
print(
"Error in BLOCK%d, re-burn it again (#%d), to fix it. "
"fail_bit=%d, num_errors=%d"
% (self.id, burns, self.fail, self.num_errors)
)
break
if not self.fail and self.num_errors == 0:
break
def burn(self):
if self.wr_bitarray.all(False):
# nothing to burn
return
before_burn_bitarray = self.bitarray[:]
assert before_burn_bitarray is not self.bitarray
self.print_block(self.wr_bitarray, "to_write")
words = self.apply_coding_scheme()
self.burn_words(words)
self.read()
if not self.is_readable():
print(
"{} ({}) is read-protected. "
"Read back the burn value is not possible.".format(
self.name, self.alias
)
)
if self.bitarray.all(False):
print("Read all '0'")
else:
# Should never happen
raise esptool.FatalError(
"The {} is read-protected but not all '0' ({})".format(
self.name, self.bitarray.hex
)
)
else:
if self.wr_bitarray == self.bitarray:
print("BURN BLOCK%-2d - OK (write block == read block)" % self.id)
elif (
self.wr_bitarray & self.bitarray == self.wr_bitarray
and self.bitarray & before_burn_bitarray == before_burn_bitarray
):
print("BURN BLOCK%-2d - OK (all write block bits are set)" % self.id)
else:
# Happens only when an efuse is written and read-protected
# in one command
self.print_block(self.wr_bitarray, "Expected")
self.print_block(self.bitarray, "Real ")
# Read-protected BLK0 values are reported back as zeros,
# raise error only for other blocks
if self.id != 0:
raise esptool.FatalError(
"Burn {} ({}) was not successful".format(self.name, self.alias)
)
self.wr_bitarray.set(0)
class EspEfusesBase(object):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
_esp = None
blocks = []
efuses = []
coding_scheme = None
force_write_always = None
batch_mode_cnt = 0
def __iter__(self):
return self.efuses.__iter__()
def get_crystal_freq(self):
return self._esp.get_crystal_freq()
def read_efuse(self, n):
"""Read the nth word of the ESP3x EFUSE region."""
return self._esp.read_efuse(n)
def read_reg(self, addr):
return self._esp.read_reg(addr)
def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0, delay_after_us=0):
return self._esp.write_reg(addr, value, mask, delay_us, delay_after_us)
def update_reg(self, addr, mask, new_val):
return self._esp.update_reg(addr, mask, new_val)
def efuse_controller_setup(self):
pass
def reconnect_chip(self, esp):
print("Re-connecting...")
baudrate = esp._port.baudrate
port = esp._port.port
esp._port.close()
return esptool.cmds.detect_chip(port, baudrate)
def get_index_block_by_name(self, name):
for block in self.blocks:
if block.name == name or name in block.alias:
return block.id
return None
def read_blocks(self):
for block in self.blocks:
block.read()
def update_efuses(self):
for efuse in self.efuses:
efuse.update(self.blocks[efuse.block].bitarray)
def burn_all(self, check_batch_mode=False):
if check_batch_mode:
if self.batch_mode_cnt != 0:
print(
"\nBatch mode is enabled, "
"the burn will be done at the end of the command."
)
return False
print("\nCheck all blocks for burn...")
print("idx, BLOCK_NAME, Conclusion")
have_wr_data_for_burn = False
for block in self.blocks:
block.check_wr_data()
if not have_wr_data_for_burn and block.get_bitstring(from_read=False).any(
True
):
have_wr_data_for_burn = True
if not have_wr_data_for_burn:
print("Nothing to burn, see messages above.")
return
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):
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
):
raise esptool.FatalError("Error(s) were detected in eFuses")
print("Reading updated efuses...")
self.read_coding_scheme()
self.read_blocks()
self.update_efuses()
return True
@staticmethod
def confirm(action, do_not_confirm):
print(
"%s%s\nThis is an irreversible operation!"
% (action, "" if action.endswith("\n") else ". ")
)
if not do_not_confirm:
print("Type 'BURN' (all capitals) to continue.")
# required for Pythons which disable line buffering, ie mingw in mintty
sys.stdout.flush()
yes = input()
if yes != "BURN":
print("Aborting.")
sys.exit(0)
def print_error_msg(self, error_msg):
if self.force_write_always is not None:
if not self.force_write_always:
error_msg += "(use '--force-write-always' option to ignore it)"
if self.force_write_always:
print(error_msg, "Skipped because '--force-write-always' option.")
else:
raise esptool.FatalError(error_msg)
def get_block_errors(self, block_num):
"""Returns (error count, failure boolean flag)"""
return self.blocks[block_num].num_errors, self.blocks[block_num].fail
class EfuseFieldBase(EfuseProtectBase):
def __init__(self, parent, param):
self.category = param.category
self.parent = parent
self.block = param.block
self.word = param.word
self.pos = param.pos
self.write_disable_bit = param.write_disable_bit
self.read_disable_bit = param.read_disable_bit
self.name = param.name
self.efuse_class = param.class_type
self.efuse_type = param.type
self.description = param.description
self.dict_value = param.dictionary
self.bit_len = param.bit_len
self.alt_names = param.alt_names
self.fail = False
self.num_errors = 0
self.bitarray = BitStream(self.bit_len)
self.bitarray.set(0)
self.update(self.parent.blocks[self.block].bitarray)
def is_field_calculated(self):
return self.word is None or self.pos is None
def check_format(self, new_value_str):
if new_value_str is None:
return new_value_str
else:
if self.efuse_type.startswith("bytes"):
if new_value_str.startswith("0x"):
# cmd line: 0x0102030405060708 .... 112233ff (hex)
# regs: 112233ff ... 05060708 01020304
# BLK: ff 33 22 11 ... 08 07 06 05 04 03 02 01
return binascii.unhexlify(new_value_str[2:])[::-1]
else:
# cmd line: 0102030405060708 .... 112233ff (string)
# regs: 04030201 08070605 ... ff332211
# BLK: 01 02 03 04 05 06 07 08 ... 11 22 33 ff
return binascii.unhexlify(new_value_str)
else:
return new_value_str
def convert_to_bitstring(self, new_value):
if isinstance(new_value, BitArray):
return new_value
else:
if self.efuse_type.startswith("bytes"):
# new_value (bytes) = [0][1][2] ... [N]
# (original data)
# in string format = [0] [1] [2] ... [N]
# (util.hexify(data, " "))
# in hex format = 0x[N]....[2][1][0]
# (from bitstring print(data))
# in reg format = [3][2][1][0] ... [N][][][]
# (as it will be in the device)
# in bitstring = [N] ... [2][1][0]
# (to get a correct bitstring need to reverse new_value)
# *[x] - means a byte.
return BitArray(bytes=new_value[::-1], length=len(new_value) * 8)
else:
try:
return BitArray(self.efuse_type + "={}".format(new_value))
except CreationError as err:
print(
"New value '{}' is not suitable for {} ({})".format(
new_value, self.name, self.efuse_type
)
)
raise esptool.FatalError(err)
def check_new_value(self, bitarray_new_value):
bitarray_old_value = self.get_bitstring() | self.get_bitstring(from_read=False)
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:
error_msg = "\tThe same value for {} ".format(self.name)
error_msg += "is already burned. Do not change the efuse."
print(error_msg)
bitarray_new_value.set(0)
elif bitarray_new_value == self.get_bitstring(from_read=False):
error_msg = "\tThe same value for {} ".format(self.name)
error_msg += "is already prepared for the burn operation."
print(error_msg)
bitarray_new_value.set(0)
else:
if self.name not in ["WR_DIS", "RD_DIS"]:
# WR_DIS, RD_DIS fields can have already set bits.
# Do not neeed to check below condition for them.
if bitarray_new_value | bitarray_old_value != bitarray_new_value:
error_msg = "\tNew value contains some bits that cannot be cleared "
error_msg += "(value will be {})".format(
bitarray_old_value | bitarray_new_value
)
self.parent.print_error_msg(error_msg)
self.check_wr_rd_protect()
def save_to_block(self, bitarray_field):
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
def save(self, new_value):
bitarray_field = self.convert_to_bitstring(new_value)
self.check_new_value(bitarray_field)
self.save_to_block(bitarray_field)
def update(self, bit_array_block):
if self.is_field_calculated():
self.bitarray.overwrite(
self.convert_to_bitstring(self.check_format(self.get())), pos=0
)
return
field_len = self.bitarray.len
bit_array_block.pos = bit_array_block.length - (
self.word * 32 + self.pos + field_len
)
self.bitarray.overwrite(bit_array_block.read(field_len), pos=0)
err_bitarray = self.parent.blocks[self.block].err_bitarray
if err_bitarray is not None:
err_bitarray.pos = err_bitarray.length - (
self.word * 32 + self.pos + field_len
)
self.fail = not err_bitarray.read(field_len).all(False)
else:
self.fail = self.parent.blocks[self.block].fail
self.num_errors = self.parent.blocks[self.block].num_errors
def get_raw(self, from_read=True):
"""Return the raw (unformatted) numeric value of the efuse bits
Returns a simple integer or (for some subclasses) a bitstring.
type: int or bool -> int
type: bytes -> bytearray
"""
return self.get_bitstring(from_read).read(self.efuse_type)
def get(self, from_read=True):
"""Get a formatted version of the efuse value, suitable for display
type: int or bool -> int
type: bytes -> string "01 02 03 04 05 06 07 08 ... ".
Byte order [0] ... [N]. dump regs: 0x04030201 0x08070605 ...
"""
if self.efuse_type.startswith("bytes"):
return util.hexify(self.get_bitstring(from_read).bytes[::-1], " ")
else:
return self.get_raw(from_read)
def get_meaning(self, from_read=True):
"""Get the meaning of efuse from dict if possible, suitable for display"""
if self.dict_value:
try:
return self.dict_value[self.get_raw(from_read)]
except KeyError:
pass
return self.get(from_read)
def get_bitstring(self, from_read=True):
if from_read:
self.bitarray.pos = 0
return self.bitarray
else:
field_len = self.bitarray.len
block = self.parent.blocks[self.block]
block.wr_bitarray.pos = block.wr_bitarray.length - (
self.word * 32 + self.pos + field_len
)
return block.wr_bitarray.read(self.bitarray.len)
def burn(self, new_value):
# Burn a efuse. Added for compatibility reason.
self.save(new_value)
self.parent.burn_all()
def get_info(self):
output = f"{self.name} (BLOCK{self.block})"
if self.block == 0:
if self.fail:
output += "[error]"
else:
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output += "[error]"
if self.efuse_class == "keyblock":
name = self.parent.blocks[self.block].key_purpose_name
if name is not None:
output += f"\n Purpose: {self.parent[name].get()}\n "
return output

View File

@@ -0,0 +1,731 @@
# This file includes the common operations with eFuses for chips
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import json
import sys
from bitstring import BitStream
import esptool
from . import base_fields
from . import util
def add_common_commands(subparsers, efuses):
class ActionEfuseValuePair(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
self._nargs = nargs
self._choices = kwargs.get("efuse_choices")
self.efuses = kwargs.get("efuses")
del kwargs["efuse_choices"]
del kwargs["efuses"]
super(ActionEfuseValuePair, self).__init__(
option_strings, dest, nargs=nargs, **kwargs
)
def __call__(self, parser, namespace, values, option_string=None):
def check_efuse_name(efuse_name, efuse_list):
if efuse_name not in self._choices:
raise esptool.FatalError(
"Invalid the efuse name '{}'. "
"Available the efuse names: {}".format(
efuse_name, self._choices
)
)
efuse_value_pairs = {}
if len(values) > 1:
if len(values) % 2:
raise esptool.FatalError(
"The list does not have a valid pair (name value) {}".format(
values
)
)
for i in range(0, len(values), 2):
efuse_name, new_value = values[i : i + 2 :]
check_efuse_name(efuse_name, self._choices)
check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
efuse_value_pairs[efuse_name] = check_arg(new_value)
else:
# For the case of compatibility, when only the efuse_name is given
# Fields with 'bitcount' and 'bool' types can be without new_value arg
efuse_name = values[0]
check_efuse_name(efuse_name, self._choices)
check_arg = base_fields.CheckArgValue(self.efuses, efuse_name)
efuse_value_pairs[efuse_name] = check_arg(None)
setattr(namespace, self.dest, efuse_value_pairs)
burn = subparsers.add_parser(
"burn_efuse", help="Burn the efuse with the specified name"
)
burn.add_argument(
"name_value_pairs",
help="Name of efuse register and New value pairs to burn",
action=ActionEfuseValuePair,
nargs="+",
metavar="[EFUSE_NAME VALUE] [{} VALUE".format(
" VALUE] [".join([e.name for e in efuses.efuses])
),
efuse_choices=[e.name for e in efuses.efuses]
+ [name for e in efuses.efuses for name in e.alt_names if name != ""],
efuses=efuses,
)
read_protect_efuse = subparsers.add_parser(
"read_protect_efuse",
help="Disable readback for the efuse with the specified name",
)
read_protect_efuse.add_argument(
"efuse_name",
help="Name of efuse register to burn",
nargs="+",
choices=[e.name for e in efuses.efuses if e.read_disable_bit is not None]
+ [
name
for e in efuses.efuses
if e.read_disable_bit is not None
for name in e.alt_names
if name != ""
],
)
write_protect_efuse = subparsers.add_parser(
"write_protect_efuse",
help="Disable writing to the efuse with the specified name",
)
write_protect_efuse.add_argument(
"efuse_name",
help="Name of efuse register to burn",
nargs="+",
choices=[e.name for e in efuses.efuses if e.write_disable_bit is not None]
+ [
name
for e in efuses.efuses
if e.write_disable_bit is not None
for name in e.alt_names
if name != ""
],
)
burn_block_data = subparsers.add_parser(
"burn_block_data",
help="Burn non-key data to EFUSE blocks. "
"(Don't use this command to burn key data for Flash Encryption or "
"ESP32 Secure Boot V1, as the byte order of keys is swapped (use burn_key)).",
)
add_force_write_always(burn_block_data)
burn_block_data.add_argument(
"--offset", "-o", help="Byte offset in the efuse block", type=int, default=0
)
burn_block_data.add_argument(
"block",
help="Efuse block to burn.",
action="append",
choices=efuses.BURN_BLOCK_DATA_NAMES,
)
burn_block_data.add_argument(
"datafile",
help="File containing data to burn into the efuse block",
action="append",
type=argparse.FileType("rb"),
)
for _ in range(0, len(efuses.BURN_BLOCK_DATA_NAMES)):
burn_block_data.add_argument(
"block",
help="Efuse block to burn.",
metavar="BLOCK",
nargs="?",
action="append",
choices=efuses.BURN_BLOCK_DATA_NAMES,
)
burn_block_data.add_argument(
"datafile",
nargs="?",
help="File containing data to burn into the efuse block",
metavar="DATAFILE",
action="append",
type=argparse.FileType("rb"),
)
set_bit_cmd = subparsers.add_parser("burn_bit", help="Burn bit in the efuse block.")
add_force_write_always(set_bit_cmd)
set_bit_cmd.add_argument(
"block", help="Efuse block to burn.", choices=efuses.BURN_BLOCK_DATA_NAMES
)
set_bit_cmd.add_argument(
"bit_number",
help="Bit number in the efuse block [0..BLK_LEN-1]",
nargs="+",
type=int,
)
subparsers.add_parser(
"adc_info",
help="Display information about ADC calibration data stored in efuse.",
)
dump_cmd = subparsers.add_parser("dump", help="Dump raw hex values of all efuses")
dump_cmd.add_argument(
"--file_name",
help="Saves dump for each block into separate file. Provide the common "
"path name /path/blk.bin, it will create: blk0.bin, blk1.bin ... blkN.bin. "
"Use burn_block_data to write it back to another chip.",
)
summary_cmd = subparsers.add_parser(
"summary", help="Print human-readable summary of efuse values"
)
summary_cmd.add_argument(
"--format",
help="Select the summary format",
choices=["summary", "json"],
default="summary",
)
summary_cmd.add_argument(
"--file",
help="File to save the efuse summary",
type=argparse.FileType("w"),
default=sys.stdout,
)
execute_scripts = subparsers.add_parser(
"execute_scripts", help="Executes scripts to burn at one time."
)
execute_scripts.add_argument(
"scripts",
help="The special format of python scripts.",
nargs="+",
type=argparse.FileType("r"),
)
execute_scripts.add_argument(
"--index",
help="integer index. "
"It allows to retrieve unique data per chip from configfiles "
"and then burn them (ex. CUSTOM_MAC, UNIQUE_ID).",
type=int,
)
execute_scripts.add_argument(
"--configfiles",
help="List of configfiles with data",
nargs="?",
action="append",
type=argparse.FileType("r"),
)
check_error_cmd = subparsers.add_parser("check_error", help="Checks eFuse errors")
check_error_cmd.add_argument(
"--recovery",
help="Recovery of BLOCKs after encoding errors",
action="store_true",
)
def add_force_write_always(p):
p.add_argument(
"--force-write-always",
help="Write the efuse even if it looks like it's already been written, "
"or is write protected. Note that this option can't disable write protection, "
"or clear any bit which has already been set.",
action="store_true",
)
def add_show_sensitive_info_option(p):
p.add_argument(
"--show-sensitive-info",
help="Show data to be burned (may expose sensitive data). "
"Enabled if --debug is used.",
action="store_true",
default=False,
)
def summary(esp, efuses, args):
"""Print a human-readable summary of efuse contents"""
ROW_FORMAT = "%-50s %-50s%s = %s %s %s"
human_output = args.format == "summary"
json_efuse = {}
if args.file != sys.stdout:
print("Saving efuse values to " + args.file.name)
if human_output:
print(
ROW_FORMAT.replace("-50", "-12")
% (
"EFUSE_NAME (Block)",
"Description",
"",
"[Meaningful Value]",
"[Readable/Writeable]",
"(Hex Value)",
),
file=args.file,
)
print("-" * 88, file=args.file)
for category in sorted(set(e.category for e in efuses), key=lambda c: c.title()):
if human_output:
print("%s fuses:" % category.title(), file=args.file)
for e in (e for e in efuses if e.category == category):
if e.efuse_type.startswith("bytes"):
raw = ""
else:
raw = "({})".format(e.get_bitstring())
(readable, writeable) = (e.is_readable(), e.is_writeable())
if readable and writeable:
perms = "R/W"
elif readable:
perms = "R/-"
elif writeable:
perms = "-/W"
else:
perms = "-/-"
base_value = e.get_meaning()
value = str(base_value)
if not readable:
count_read_disable_bits = e.get_count_read_disable_bits()
if count_read_disable_bits == 2:
# On the C2 chip, BLOCK_KEY0 has two read protection bits [0, 1]
# related to the lower and higher part of the block.
v = [value[: (len(value) // 2)], value[(len(value) // 2) :]]
for i in range(count_read_disable_bits):
if not e.is_readable(blk_part=i):
v[i] = v[i].replace("0", "?")
value = "".join(v)
else:
value = value.replace("0", "?")
if human_output:
print(
ROW_FORMAT
% (
e.get_info(),
e.description[:50],
"\n " if len(value) > 20 else "",
value,
perms,
raw,
),
file=args.file,
)
desc_len = len(e.description[50:])
if desc_len:
desc_len += 50
for i in range(50, desc_len, 50):
print(
"%-50s %-50s" % ("", e.description[i : (50 + i)]),
file=args.file,
)
if args.format == "json":
json_efuse[e.name] = {
"name": e.name,
"value": base_value if readable else value,
"readable": readable,
"writeable": writeable,
"description": e.description,
"category": e.category,
"block": e.block,
"word": e.word,
"pos": e.pos,
"efuse_type": e.efuse_type,
"bit_len": e.bit_len,
}
if human_output:
print("", file=args.file)
if human_output:
print(efuses.summary(), file=args.file)
warnings = efuses.get_coding_scheme_warnings()
if warnings:
print(
"WARNING: Coding scheme has encoding bit error warnings", file=args.file
)
if args.file != sys.stdout:
args.file.close()
print("Done")
if args.format == "json":
json.dump(json_efuse, args.file, sort_keys=True, indent=4)
print("")
def dump(esp, efuses, args):
"""Dump raw efuse data registers"""
# Using --debug option allows to print dump.
# Nothing to do here. The log will be printed
# during EspEfuses.__init__() in self.read_blocks()
if args.file_name:
# save dump to the file
for block in efuses.blocks:
file_dump_name = args.file_name
place_for_index = file_dump_name.find(".bin")
file_dump_name = (
file_dump_name[:place_for_index]
+ str(block.id)
+ file_dump_name[place_for_index:]
)
print(file_dump_name)
with open(file_dump_name, "wb") as f:
block.get_bitstring().byteswap()
block.get_bitstring().tofile(f)
def burn_efuse(esp, efuses, args):
def print_attention(blocked_efuses_after_burn):
if len(blocked_efuses_after_burn):
print(
" ATTENTION! This BLOCK uses NOT the NONE coding scheme "
"and after 'BURN', these efuses can not be burned in the feature:"
)
for i in range(0, len(blocked_efuses_after_burn), 5):
print(
" ",
"".join("{}".format(blocked_efuses_after_burn[i : i + 5 :])),
)
efuse_name_list = [name for name in args.name_value_pairs.keys()]
burn_efuses_list = [efuses[name] for name in efuse_name_list]
old_value_list = [efuses[name].get_raw() for name in efuse_name_list]
new_value_list = [value for value in args.name_value_pairs.values()]
util.check_duplicate_name_in_list(efuse_name_list)
attention = ""
print("The efuses to burn:")
for block in efuses.blocks:
burn_list_a_block = [e for e in burn_efuses_list if e.block == block.id]
if len(burn_list_a_block):
print(" from BLOCK%d" % (block.id))
for field in burn_list_a_block:
print(" - %s" % (field.name))
if (
efuses.blocks[field.block].get_coding_scheme()
!= efuses.REGS.CODING_SCHEME_NONE
):
using_the_same_block_names = [
e.name for e in efuses if e.block == field.block
]
wr_names = [e.name for e in burn_list_a_block]
blocked_efuses_after_burn = [
name
for name in using_the_same_block_names
if name not in wr_names
]
attention = " (see 'ATTENTION!' above)"
if attention:
print_attention(blocked_efuses_after_burn)
print("\nBurning efuses{}:".format(attention))
for efuse, new_value in zip(burn_efuses_list, new_value_list):
print(
"\n - '{}' ({}) {} -> {}".format(
efuse.name,
efuse.description,
efuse.get_bitstring(),
efuse.convert_to_bitstring(new_value),
)
)
efuse.save(new_value)
print()
if "ENABLE_SECURITY_DOWNLOAD" in efuse_name_list:
print(
"ENABLE_SECURITY_DOWNLOAD -> 1: eFuses will not be read back "
"for confirmation because this mode disables "
"any SRAM and register operations."
)
print(" espefuse will not work.")
print(" esptool can read/write only flash.")
if "DIS_DOWNLOAD_MODE" in efuse_name_list:
print(
"DIS_DOWNLOAD_MODE -> 1: eFuses will not be read back for "
"confirmation because this mode disables any communication with the chip."
)
print(
" espefuse/esptool will not work because "
"they will not be able to connect to the chip."
)
if (
esp.CHIP_NAME == "ESP32"
and esp.get_chip_revision() >= 300
and "UART_DOWNLOAD_DIS" in efuse_name_list
):
print(
"UART_DOWNLOAD_DIS -> 1: eFuses will be read for confirmation, "
"but after that connection to the chip will become impossible."
)
print(" espefuse/esptool will not work.")
if not efuses.burn_all(check_batch_mode=True):
return
print("Checking efuses...")
raise_error = False
for efuse, old_value, new_value in zip(
burn_efuses_list, old_value_list, new_value_list
):
if not efuse.is_readable():
print(
"Efuse %s is read-protected. Read back the burn value is not possible."
% efuse.name
)
else:
new_value = efuse.convert_to_bitstring(new_value)
burned_value = efuse.get_bitstring()
if burned_value != new_value:
print(
burned_value,
"->",
new_value,
"Efuse %s failed to burn. Protected?" % efuse.name,
)
raise_error = True
if raise_error:
raise esptool.FatalError("The burn was not successful.")
else:
print("Successful")
def read_protect_efuse(esp, efuses, args):
util.check_duplicate_name_in_list(args.efuse_name)
for efuse_name in args.efuse_name:
efuse = efuses[efuse_name]
if not efuse.is_readable():
print("Efuse %s is already read protected" % efuse.name)
else:
if esp.CHIP_NAME == "ESP32":
if (
efuse_name == "BLOCK2"
and not efuses["ABS_DONE_0"].get()
and esp.get_chip_revision() >= 300
):
if efuses["ABS_DONE_1"].get():
raise esptool.FatalError(
"Secure Boot V2 is on (ABS_DONE_1 = True), "
"BLOCK2 must be readable, stop this operation!"
)
else:
print(
"If Secure Boot V2 is used, BLOCK2 must be readable, "
"please stop this operation!"
)
elif esp.CHIP_NAME == "ESP32-C2":
error = (
not efuses["XTS_KEY_LENGTH_256"].get()
and efuse_name == "BLOCK_KEY0"
)
error |= efuses["SECURE_BOOT_EN"].get() and efuse_name in [
"BLOCK_KEY0",
"BLOCK_KEY0_HI_128",
]
if error:
raise esptool.FatalError(
"%s must be readable, stop this operation!" % efuse_name
)
else:
for block in efuses.Blocks.BLOCKS:
block = efuses.Blocks.get(block)
if block.name == efuse_name and block.key_purpose is not None:
if not efuses[block.key_purpose].need_rd_protect(
efuses[block.key_purpose].get()
):
raise esptool.FatalError(
"%s must be readable, stop this operation!" % efuse_name
)
break
# make full list of which efuses will be disabled
# (ie share a read disable bit)
all_disabling = [
e for e in efuses if e.read_disable_bit == efuse.read_disable_bit
]
names = ", ".join(e.name for e in all_disabling)
print(
"Permanently read-disabling efuse%s %s"
% ("s" if len(all_disabling) > 1 else "", names)
)
efuse.disable_read()
if not efuses.burn_all(check_batch_mode=True):
return
print("Checking efuses...")
raise_error = False
for efuse_name in args.efuse_name:
efuse = efuses[efuse_name]
if efuse.is_readable():
print("Efuse %s is not read-protected." % efuse.name)
raise_error = True
if raise_error:
raise esptool.FatalError("The burn was not successful.")
else:
print("Successful")
def write_protect_efuse(esp, efuses, args):
util.check_duplicate_name_in_list(args.efuse_name)
for efuse_name in args.efuse_name:
efuse = efuses[efuse_name]
if not efuse.is_writeable():
print("Efuse %s is already write protected" % efuse.name)
else:
# make full list of which efuses will be disabled
# (ie share a write disable bit)
all_disabling = [
e for e in efuses if e.write_disable_bit == efuse.write_disable_bit
]
names = ", ".join(e.name for e in all_disabling)
print(
"Permanently write-disabling efuse%s %s"
% ("s" if len(all_disabling) > 1 else "", names)
)
efuse.disable_write()
if not efuses.burn_all(check_batch_mode=True):
return
print("Checking efuses...")
raise_error = False
for efuse_name in args.efuse_name:
efuse = efuses[efuse_name]
if efuse.is_writeable():
print("Efuse %s is not write-protected." % efuse.name)
raise_error = True
if raise_error:
raise esptool.FatalError("The burn was not successful.")
else:
print("Successful")
def burn_block_data(esp, efuses, args):
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
datafile_list = args.datafile[
0 : len([name for name in args.datafile if name is not None]) :
]
efuses.force_write_always = args.force_write_always
util.check_duplicate_name_in_list(block_name_list)
if args.offset and len(block_name_list) > 1:
raise esptool.FatalError(
"The 'offset' option is not applicable when a few blocks are passed. "
"With 'offset', should only one block be used."
)
else:
offset = args.offset
if offset:
num_block = efuses.get_index_block_by_name(block_name_list[0])
block = efuses.blocks[num_block]
num_bytes = block.get_block_len()
if offset >= num_bytes:
raise esptool.FatalError(
"Invalid offset: the block%d only holds %d bytes."
% (block.id, num_bytes)
)
if len(block_name_list) != len(datafile_list):
raise esptool.FatalError(
"The number of block_name (%d) and datafile (%d) should be the same."
% (len(block_name_list), len(datafile_list))
)
for block_name, datafile in zip(block_name_list, datafile_list):
num_block = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[num_block]
data = datafile.read()
num_bytes = block.get_block_len()
if offset != 0:
data = (b"\x00" * offset) + data
data = data + (b"\x00" * (num_bytes - len(data)))
if len(data) != num_bytes:
raise esptool.FatalError(
"Data does not fit: the block%d size is %d bytes, "
"data file is %d bytes, offset %d"
% (block.id, num_bytes, len(data), offset)
)
print(
"[{:02}] {:20} size={:02} bytes, offset={:02} - > [{}].".format(
block.id, block.name, len(data), offset, util.hexify(data, " ")
)
)
block.save(data)
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_bit(esp, efuses, args):
efuses.force_write_always = args.force_write_always
num_block = efuses.get_index_block_by_name(args.block)
block = efuses.blocks[num_block]
data_block = BitStream(block.get_block_len() * 8)
data_block.set(0)
try:
data_block.set(True, args.bit_number)
except IndexError:
raise esptool.FatalError(
"%s has bit_number in [0..%d]" % (args.block, data_block.len - 1)
)
data_block.reverse()
print(
"bit_number: "
"[%-03d]........................................................[0]"
% (data_block.len - 1)
)
print("BLOCK%-2d :" % block.id, data_block)
block.print_block(data_block, "regs_to_write", debug=True)
block.save(data_block.bytes[::-1])
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def get_error_summary(efuses):
error_in_blocks = efuses.get_coding_scheme_warnings()
if not error_in_blocks:
return False
writable = True
for blk in efuses.blocks:
if blk.fail or blk.num_errors:
if blk.id == 0:
for field in efuses:
if field.block == blk.id and (field.fail or field.num_errors):
wr = "writable" if field.is_writeable() else "not writable"
writable &= wr == "writable"
name = field.name
val = field.get()
print(f"BLOCK{field.block:<2}: {name:<40} = {val:<8} ({wr})")
else:
wr = "writable" if blk.is_writeable() else "not writable"
writable &= wr == "writable"
name = f"{blk.name} [ERRORS:{blk.num_errors} FAIL:{int(blk.fail)}]"
val = str(blk.get_bitstring())
print(f"BLOCK{blk.id:<2}: {name:<40} = {val:<8} ({wr})")
if not writable and error_in_blocks:
print("Not all errors can be fixed because some fields are write-protected!")
return True
def check_error(esp, efuses, args):
error_in_blocks = get_error_summary(efuses)
if args.recovery and error_in_blocks:
confirmed = False
for block in reversed(efuses.blocks):
if block.fail or block.num_errors > 0:
if not block.get_bitstring().all(False):
block.save(block.get_bitstring().bytes[::-1])
if not confirmed:
confirmed = True
efuses.confirm(
"Recovery of block coding errors", args.do_not_confirm
)
block.burn()
if confirmed:
efuses.update_efuses()
error_in_blocks = get_error_summary(efuses)
if error_in_blocks:
raise esptool.FatalError("Error(s) were detected in eFuses")
print("No errors detected")

View File

@@ -0,0 +1,225 @@
# This file describes eFuses controller for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import re
from bitstring import BitStream
class EmulateEfuseControllerBase(object):
"""The class for virtual efuse operations. Using for HOST_TEST."""
CHIP_NAME = ""
mem = None
debug = False
Blocks = None
Fields = None
REGS = None
def __init__(self, efuse_file=None, debug=False):
self.debug = debug
self.efuse_file = efuse_file
if self.efuse_file:
try:
self.mem = BitStream(
bytes=open(self.efuse_file, "rb").read(),
length=self.REGS.EFUSE_MEM_SIZE * 8,
)
except (ValueError, FileNotFoundError):
# the file is empty or does not fit the length.
self.mem = BitStream(length=self.REGS.EFUSE_MEM_SIZE * 8)
self.mem.set(0)
self.mem.tofile(open(self.efuse_file, "a+b"))
else:
# efuse_file is not provided
# it means we do not want to keep the result of efuse operations
self.mem = BitStream(self.REGS.EFUSE_MEM_SIZE * 8)
self.mem.set(0)
""" esptool method start >> """
def get_chip_description(self):
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
def get_chip_revision(self):
return self.get_major_chip_version() * 100 + self.get_minor_chip_version()
def read_efuse(self, n, block=0):
"""Read the nth word of the ESP3x EFUSE region."""
blk = self.Blocks.get(self.Blocks.BLOCKS[block])
return self.read_reg(blk.rd_addr + (4 * n))
def read_reg(self, addr):
self.mem.pos = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
return self.mem.read("uint:32")
def write_reg(self, addr, value, mask=0xFFFFFFFF, delay_us=0, delay_after_us=0):
self.mem.pos = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
self.mem.overwrite("uint:32={}".format(value & mask))
self.handle_writing_event(addr, value)
def update_reg(self, addr, mask, new_val):
position = self.mem.length - ((addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + 32)
self.mem.pos = position
cur_val = self.mem.read("uint:32")
self.mem.pos = position
self.mem.overwrite("uint:32={}".format(cur_val | (new_val & mask)))
def write_efuse(self, n, value, block=0):
"""Write the nth word of the ESP3x EFUSE region."""
blk = self.Blocks.get(self.Blocks.BLOCKS[block])
self.write_reg(blk.wr_addr + (4 * n), value)
""" << esptool method end """
def handle_writing_event(self, addr, value):
self.save_to_file()
def save_to_file(self):
if self.efuse_file:
with open(self.efuse_file, "wb") as f:
self.mem.tofile(f)
def handle_coding_scheme(self, blk, data):
return data
def copy_blocks_wr_regs_to_rd_regs(self, updated_block=None):
for b in reversed(self.Blocks.BLOCKS):
blk = self.Blocks.get(b)
if updated_block is not None:
if blk.id != updated_block:
continue
data = self.read_block(blk.id, wr_regs=True)
if self.debug:
print(blk.name, data.hex)
plain_data = self.handle_coding_scheme(blk, data)
plain_data = self.check_wr_protection_area(blk.id, plain_data)
self.update_block(blk, plain_data)
def clean_blocks_wr_regs(self):
for b in self.Blocks.BLOCKS:
blk = self.Blocks.get(b)
for offset in range(0, blk.len * 4, 4):
wr_addr = blk.wr_addr + offset
self.write_reg(wr_addr, 0)
def read_field(self, name, bitstring=True):
for field in self.Fields.EFUSES:
if field.name == name:
self.read_block(field.block)
block = self.read_block(field.block)
if field.type.startswith("bool"):
field_len = 1
else:
field_len = int(re.search(r"\d+", field.type).group())
if field.type.startswith("bytes"):
field_len *= 8
block.pos = block.length - (field.word * 32 + field.pos + field_len)
if bitstring:
return block.read(field_len)
else:
return block.read(field.type)
return None
def get_bitlen_of_block(self, blk, wr=False):
return 32 * blk.len
def read_block(self, idx, wr_regs=False):
block = None
for b in self.Blocks.BLOCKS:
blk = self.Blocks.get(b)
if blk.id == idx:
blk_len_bits = self.get_bitlen_of_block(blk, wr=wr_regs)
addr = blk.wr_addr if wr_regs else blk.rd_addr
self.mem.pos = self.mem.length - (
(addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + blk_len_bits
)
block = self.mem.read(blk_len_bits)
break
return block
def update_block(self, blk, wr_data):
wr_data = self.read_block(blk.id) | wr_data
self.overwrite_mem_from_block(blk, wr_data)
def overwrite_mem_from_block(self, blk, wr_data):
self.mem.pos = self.mem.length - (
(blk.rd_addr - self.REGS.DR_REG_EFUSE_BASE) * 8 + wr_data.len
)
self.mem.overwrite(wr_data)
def check_wr_protection_area(self, num_blk, wr_data):
# checks fields which have the write protection bit.
# if the write protection bit is set, we need to protect that area from changes.
write_disable_bit = self.read_field("WR_DIS", bitstring=False)
mask_wr_data = BitStream(len(wr_data))
mask_wr_data.set(0)
blk = self.Blocks.get(self.Blocks.BLOCKS[num_blk])
if blk.write_disable_bit is not None and write_disable_bit & (
1 << blk.write_disable_bit
):
mask_wr_data.set(1)
else:
for field in self.Fields.EFUSES:
if blk.id == field.block and field.block == num_blk:
if field.write_disable_bit is not None and write_disable_bit & (
1 << field.write_disable_bit
):
data = self.read_field(field.name)
data.set(1)
mask_wr_data.pos = mask_wr_data.length - (
field.word * 32 + field.pos + data.len
)
mask_wr_data.overwrite(data)
mask_wr_data.invert()
return wr_data & mask_wr_data
def check_rd_protection_area(self):
# checks fields which have the read protection bits.
# if the read protection bit is set then we need to reset this field to 0.
read_disable_bit = self.read_field("RD_DIS", bitstring=False)
for b in self.Blocks.BLOCKS:
blk = self.Blocks.get(b)
block = self.read_block(blk.id)
if blk.read_disable_bit is not None and read_disable_bit & (
1 << blk.read_disable_bit
):
block.set(0)
else:
for field in self.Fields.EFUSES:
if (
blk.id == field.block
and field.read_disable_bit is not None
and read_disable_bit & (1 << field.read_disable_bit)
):
raw_data = self.read_field(field.name)
raw_data.set(0)
block.pos = block.length - (
field.word * 32 + field.pos + raw_data.length
)
block.overwrite(BitStream(raw_data.length))
self.overwrite_mem_from_block(blk, block)
def clean_mem(self):
self.mem.set(0)
if self.efuse_file:
with open(self.efuse_file, "wb") as f:
self.mem.tofile(f)
class FatalError(RuntimeError):
"""
Wrapper class for runtime errors that aren't caused by internal bugs
"""
def __init__(self, message):
RuntimeError.__init__(self, message)
@staticmethod
def WithResult(message, result):
return FatalError(result)

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,141 @@
# This file describes eFuses controller for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import time
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operations. Using for HOST_TEST."""
CHIP_NAME = "ESP32"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
""" esptool method start >> """
def get_major_chip_version(self):
return 3
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 40 # MHz (common for all chips)
def read_reg(self, addr):
if addr == self.REGS.APB_CTL_DATE_ADDR:
return self.REGS.APB_CTL_DATE_V << self.REGS.APB_CTL_DATE_S
else:
val = 0
if addr == self.REGS.EFUSE_BLK0_RDATA3_REG:
val = self.REGS.EFUSE_RD_CHIP_VER_REV1
if addr == self.REGS.EFUSE_BLK0_RDATA5_REG:
val = self.REGS.EFUSE_RD_CHIP_VER_REV2
return val | super(EmulateEfuseController, self).read_reg(addr)
""" << esptool method end """
def send_burn_cmd(self):
def wait_idle():
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
if self.read_reg(self.REGS.EFUSE_REG_CMD) == 0:
return
raise FatalError(
"Timed out waiting for Efuse controller command to complete"
)
self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_WRITE)
wait_idle()
self.write_reg(self.REGS.EFUSE_REG_CONF, self.REGS.EFUSE_CONF_READ)
self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_READ)
wait_idle()
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_REG_CMD:
if value == self.REGS.EFUSE_CMD_WRITE:
self.write_reg(addr, 0)
elif value == self.REGS.EFUSE_CMD_READ:
self.copy_blocks_wr_regs_to_rd_regs()
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.save_to_file()
def read_raw_coding_scheme(self):
return (
self.read_efuse(self.REGS.EFUSE_CODING_SCHEME_WORD)
& self.REGS.EFUSE_CODING_SCHEME_MASK
)
def write_raw_coding_scheme(self, value):
self.write_efuse(
self.REGS.EFUSE_CODING_SCHEME_WORD,
value & self.REGS.EFUSE_CODING_SCHEME_MASK,
)
self.send_burn_cmd()
if value != self.read_raw_coding_scheme():
raise FatalError(
"Error during a burning process to set the new coding scheme"
)
print("Set coding scheme = %d" % self.read_raw_coding_scheme())
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
return 32 * blk.len
else:
coding_scheme = self.read_raw_coding_scheme()
if coding_scheme == self.REGS.CODING_SCHEME_NONE:
return 32 * blk.len
elif coding_scheme == self.REGS.CODING_SCHEME_34:
if wr:
return 32 * 8
else:
return 32 * blk.len * 3 // 4
else:
raise FatalError(
"The {} coding scheme is not supported".format(coding_scheme)
)
def handle_coding_scheme(self, blk, data):
# it verifies the coding scheme part of data and returns just data
if blk.id != 0 and self.read_raw_coding_scheme() == self.REGS.CODING_SCHEME_34:
# CODING_SCHEME 3/4 applied only for BLK1..3
# Takes 24 byte sequence to be represented in 3/4 encoding,
# returns 8 words suitable for writing "encoded" to an efuse block
data.pos = 0
for _ in range(0, 4):
xor_res = 0
mul_res = 0
chunk_data = data.readlist("8*uint:8")
chunk_data = chunk_data[::-1]
for i in range(0, 6):
byte_data = chunk_data[i]
xor_res ^= byte_data
mul_res += (i + 1) * bin(byte_data).count("1")
if xor_res != chunk_data[6] or mul_res != chunk_data[7]:
print(
"xor_res ",
xor_res,
chunk_data[6],
"mul_res",
mul_res,
chunk_data[7],
)
raise FatalError("Error in coding scheme data")
# cut the coded data
for i in range(0, 4):
del data[i * 6 * 8 : (i * 6 * 8) + 16]
return data

View File

@@ -0,0 +1,466 @@
# This file describes eFuses for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import time
import esptool
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is the same as len of a block.
return self.len
def __init__(self, parent, param, skip_read=False):
if skip_read:
parent.coding_scheme = parent.REGS.CODING_SCHEME_NONE
else:
if parent.coding_scheme is None:
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_34:
# CODING_SCHEME 3/4 applied only for BLK1..3
# Takes 24 byte sequence to be represented in 3/4 encoding,
# returns 8 words suitable for writing "encoded" to an efuse block
if len(data) != 24:
raise esptool.FatalError("Should take 24 bytes for 3/4 encoding.")
data = data[:24]
outbits = b""
while len(data) > 0: # process in chunks of 6 bytes
bits = data[0:6]
data = data[6:]
xor_res = 0
mul_res = 0
index = 1
for b in struct.unpack("B" * 6, bits):
xor_res ^= b
mul_res += index * util.popcnt(b)
index += 1
outbits += bits
outbits += struct.pack("BB", xor_res, mul_res)
words = struct.unpack("<" + "I" * (len(outbits) // 4), outbits)
# returns 8 words
else:
# CODING_SCHEME NONE applied for BLK0 and BLK1..3
# BLK0 len = 7 words, BLK1..3 len = 8 words.
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 7 words for BLK0 or 8 words for BLK1..3
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32 chip but got for '%s'."
% (esp.CHIP_NAME)
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS_256
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.ADC_CALIBRATION
]
else:
if self.coding_scheme == self.REGS.CODING_SCHEME_NONE:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.KEYBLOCKS_256
]
elif self.coding_scheme == self.REGS.CODING_SCHEME_34:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.KEYBLOCKS_192
]
else:
raise esptool.FatalError(
"The coding scheme (%d) - is not supported" % self.coding_scheme
)
if self["MAC_VERSION"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
]
if self["BLK3_PART_RESERVE"].get():
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.ADC_CALIBRATION
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.CUSTOM_MAC:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CUSTOM_MAC
]
new_fields = True
for efuse in self.Fields.ADC_CALIBRATION:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.ADC_CALIBRATION
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = (
self.read_efuse(self.REGS.EFUSE_CODING_SCHEME_WORD)
& self.REGS.EFUSE_CODING_SCHEME_MASK
)
def print_status_regs(self):
print("")
print(
"{:27} 0x{:08x}".format(
"EFUSE_REG_DEC_STATUS", self.read_reg(self.REGS.EFUSE_REG_DEC_STATUS)
)
)
def write_efuses(self, block):
"""Write the values in the efuse write registers to
the efuse hardware, then refresh the efuse read registers.
"""
# Configure clock
apb_freq = self.get_crystal_freq()
clk_sel0, clk_sel1, dac_clk_div = self.REGS.EFUSE_CLK_SETTINGS[apb_freq]
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_MASK, dac_clk_div
)
self.update_reg(
self.REGS.EFUSE_CLK_REG, self.REGS.EFUSE_CLK_SEL0_MASK, clk_sel0
)
self.update_reg(
self.REGS.EFUSE_CLK_REG, self.REGS.EFUSE_CLK_SEL1_MASK, clk_sel1
)
self.write_reg(self.REGS.EFUSE_REG_CONF, self.REGS.EFUSE_CONF_WRITE)
self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_WRITE)
self.efuse_read()
return self.get_coding_scheme_warnings(silent=True)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
if self.read_reg(self.REGS.EFUSE_REG_CMD) == 0:
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_REG_CONF, self.REGS.EFUSE_CONF_READ)
self.write_reg(self.REGS.EFUSE_REG_CMD, self.REGS.EFUSE_CMD_READ)
self.wait_efuse_idle()
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors.
Meaningless for default coding scheme (0)
"""
err = (
self.read_reg(self.REGS.EFUSE_REG_DEC_STATUS)
& self.REGS.EFUSE_REG_DEC_STATUS_MASK
)
for block in self.blocks:
if block.id != 0:
block.num_errors = 0
block.fail = err != 0
if not silent and block.fail:
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or err) and not silent:
self.print_status_regs()
return err != 0
def summary(self):
if self["XPD_SDIO_FORCE"].get() == 0:
output = "Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V)"
elif self["XPD_SDIO_REG"].get() == 0:
output = "Flash voltage (VDD_SDIO) internal regulator disabled by efuse."
elif self["XPD_SDIO_TIEH"].get() == 0:
output = "Flash voltage (VDD_SDIO) set to 1.8V by efuse."
else:
output = "Flash voltage (VDD_SDIO) set to 3.3V by efuse."
return output
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"spipin": EfuseSpiPinField,
"vref": EfuseVRefField,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
"pkg": EfusePkg,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseMacField(EfuseField):
"""
Supports: MAC and CUSTOM_MAC fields.
(if MAC_VERSION == 1 then the CUSTOM_MAC is used)
"""
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
if new_value_str.count(":") != 5:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "")
if len(hexad) != 12:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal number "
"(12 hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
@staticmethod
def get_and_check(raw_mac, stored_crc):
computed_crc = EfuseMacField.calc_crc(raw_mac)
if computed_crc == stored_crc:
valid_msg = "(CRC 0x%02x OK)" % stored_crc
else:
valid_msg = "(CRC 0x%02x invalid - calculated 0x%02x)" % (
stored_crc,
computed_crc,
)
return "%s %s" % (util.hexify(raw_mac, ":"), valid_msg)
@staticmethod
def calc_crc(raw_mac):
"""
This algorithm is the equivalent of esp_crc8() in ESP32 ROM code
This is CRC-8 w/ inverted polynomial value 0x8C & initial value 0x00.
"""
result = 0x00
for b in struct.unpack("B" * 6, raw_mac):
result ^= b
for _ in range(8):
lsb = result & 1
result >>= 1
if lsb != 0:
result ^= 0x8C
return result
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
stored_crc = self.parent["CUSTOM_MAC_CRC"].get(from_read)
else:
mac = self.get_raw(from_read)
stored_crc = self.parent["MAC_CRC"].get(from_read)
return EfuseMacField.get_and_check(mac, stored_crc)
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
# Writing the BLK3:
# - MAC_VERSION = 1
# - CUSTOM_MAC = AB:CD:EF:01:02:03
# - CUSTOM_MAC_CRC = crc8(CUSTOM_MAC)
mac_version = self.parent["MAC_VERSION"]
if mac_version.get() == 0:
mac_version_value = 1
print_field(mac_version, hex(mac_version_value))
mac_version.save(mac_version_value)
else:
if mac_version.get() != 1:
if not self.parent.force_write_always:
raise esptool.FatalError(
"MAC_VERSION = {}, should be 0 or 1.".format(
mac_version.get()
)
)
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
crc_val = self.calc_crc(new_value)
crc_field = self.parent["CUSTOM_MAC_CRC"]
print_field(crc_field, hex(crc_val))
crc_field.save(crc_val)
else:
# Writing the BLK0 default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError("Writing Factory MAC address is not supported")
class EfuseWafer(EfuseField):
def get(self, from_read=True):
rev_bit0 = self.parent["CHIP_VER_REV1"].get(from_read)
assert self.parent["CHIP_VER_REV1"].bit_len == 1
rev_bit1 = self.parent["CHIP_VER_REV2"].get(from_read)
assert self.parent["CHIP_VER_REV2"].bit_len == 1
apb_ctl_date = self.parent.read_reg(self.parent.REGS.APB_CTL_DATE_ADDR)
rev_bit2 = (
apb_ctl_date >> self.parent.REGS.APB_CTL_DATE_S
) & self.parent.REGS.APB_CTL_DATE_V
combine_value = (rev_bit2 << 2) | (rev_bit1 << 1) | rev_bit0
revision = {
0: 0,
1: 1,
3: 2,
7: 3,
}.get(combine_value, 0)
return revision
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfusePkg(EfuseField):
def get(self, from_read=True):
lo_bits = self.parent["CHIP_PACKAGE"].get(from_read)
hi_bits = self.parent["CHIP_PACKAGE_4BIT"].get(from_read)
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseSpiPinField(EfuseField):
def get(self, from_read=True):
val = self.get_raw(from_read)
if val >= 30:
val += 2 # values 30,31 map to 32, 33
return val
def check_format(self, new_value_str):
if new_value_str is None:
return new_value_str
new_value_int = int(new_value_str, 0)
if new_value_int in [30, 31]:
raise esptool.FatalError(
"IO pins 30 & 31 cannot be set for SPI flash. 0-29, 32 & 33 only."
)
elif new_value_int > 33:
raise esptool.FatalError(
"IO pin %d cannot be set for SPI flash. 0-29, 32 & 33 only."
% new_value_int
)
elif new_value_int in [32, 33]:
return str(new_value_int - 2)
else:
return new_value_str
class EfuseVRefField(EfuseField):
VREF_OFFSET = 1100 # ideal efuse value in mV
VREF_STEP_SIZE = 7 # 1 count in efuse == 7mV
VREF_SIGN_BIT = 0x10
VREF_MAG_BITS = 0x0F
def get(self, from_read=True):
val = self.get_raw(from_read)
# sign-magnitude format
if val & self.VREF_SIGN_BIT:
val = -(val & self.VREF_MAG_BITS)
else:
val = val & self.VREF_MAG_BITS
val *= self.VREF_STEP_SIZE
return self.VREF_OFFSET + val
def save(self, new_value):
raise esptool.FatalError("Writing to VRef is not supported.")
class EfuseAdcPointCalibration(EfuseField):
TP_OFFSET = { # See TP_xxxx_OFFSET in esp_adc_cal.c in ESP-IDF
"ADC1_TP_LOW": 278,
"ADC2_TP_LOW": 421,
"ADC1_TP_HIGH": 3265,
"ADC2_TP_HIGH": 3406,
}
SIGN_BIT = (0x40, 0x100) # LOW, HIGH (2s complement format)
STEP_SIZE = 4
def get(self, from_read=True):
idx = 0 if self.name.endswith("LOW") else 1
sign_bit = self.SIGN_BIT[idx]
offset = self.TP_OFFSET[self.name]
raw = self.get_raw()
delta = (raw & (sign_bit - 1)) - (raw & sign_bit)
return offset + (delta * self.STEP_SIZE)

View File

@@ -0,0 +1,179 @@
# This file describes eFuses fields and registers for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import copy
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x011C + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x3FF5A000
EFUSE_REG_CONF = DR_REG_EFUSE_BASE + 0x0FC
EFUSE_CONF_WRITE = 0x5A5A
EFUSE_CONF_READ = 0x5AA5
EFUSE_REG_CMD = DR_REG_EFUSE_BASE + 0x104
EFUSE_CMD_OP_MASK = 0x3
EFUSE_CMD_WRITE = 0x2
EFUSE_CMD_READ = 0x1
# 3/4 Coding scheme warnings registers
EFUSE_REG_DEC_STATUS = DR_REG_EFUSE_BASE + 0x11C
EFUSE_REG_DEC_STATUS_MASK = 0xFFF
# Coding Scheme
EFUSE_CODING_SCHEME_WORD = 6
EFUSE_CODING_SCHEME_MASK = 0x3
# Efuse clock control
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x118
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x0F8
EFUSE_DAC_CLK_DIV_MASK = 0xFF
EFUSE_CLK_SEL0_MASK = 0x00FF
EFUSE_CLK_SEL1_MASK = 0xFF00
EFUSE_CLK_SETTINGS = {
# APB freq: clk_sel0, clk_sel1, dac_clk_div
# Taken from TRM chapter "eFuse Controller": Timing Configuration
# 80 is here for completeness only as esptool never sets an 80MHz APB clock
26: (250, 255, 52),
40: (160, 255, 80),
80: (80, 128, 100),
}
DR_REG_SYSCON_BASE = 0x3FF66000
APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C
APB_CTL_DATE_V = 0x1
APB_CTL_DATE_S = 31
EFUSE_BLK0_RDATA3_REG = DR_REG_EFUSE_BASE + 0x00C
EFUSE_RD_CHIP_VER_REV1 = 1 << 15
EFUSE_BLK0_RDATA5_REG = DR_REG_EFUSE_BASE + 0x014
EFUSE_RD_CHIP_VER_REV2 = 1 << 20
class EfuseDefineBlocks(EfuseBlocksBase):
__base_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_regs + 0x000, __base_regs + 0x01C, None, None, 7, None),
("BLOCK1", ["flash_encryption"], 1, __base_regs + 0x038, __base_regs + 0x098, 7, 0, 8, None),
("BLOCK2", ["secure_boot_v1", "secure_boot_v2"], 2, __base_regs + 0x058, __base_regs + 0x0B8, 8, 1, 8, None),
("BLOCK3", [], 3, __base_regs + 0x078, __base_regs + 0x0D8, 9, 2, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
self.EFUSES = []
# if MAC_VERSION is set "1", these efuse fields are in BLOCK3:
self.CUSTOM_MAC = []
# The len of fields depends on coding scheme: for CODING_SCHEME_NONE
self.KEYBLOCKS_256 = []
# The len of fields depends on coding scheme: for CODING_SCHEME_34
self.KEYBLOCKS_192 = []
# if BLK3_PART_RESERVE is set, these efuse fields are in BLOCK3:
self.ADC_CALIBRATION = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name == "BLOCK1" or efuse.name == "BLOCK2":
self.KEYBLOCKS_256.append(efuse)
BLOCK = copy.deepcopy(efuse)
BLOCK.type = "bytes:24"
BLOCK.bit_len = 24 * 8
self.KEYBLOCKS_192.append(BLOCK)
self.ALL_EFUSES[i] = None
elif efuse.name == "MAC_VERSION":
# A field from BLOCK3, It is used as a template
BLOCK3 = copy.deepcopy(efuse)
BLOCK3.name = "BLOCK3"
BLOCK3.block = 3
BLOCK3.word = 0
BLOCK3.pos = 0
BLOCK3.bit_len = 32 * 8
BLOCK3.type = "bytes:32"
BLOCK3.category = "security"
BLOCK3.class_type = "keyblock"
BLOCK3.description = "Variable Block 3"
self.KEYBLOCKS_256.append(BLOCK3)
BLOCK3 = copy.deepcopy(BLOCK3)
BLOCK3.type = "bytes:24"
BLOCK3.bit_len = 24 * 8
self.KEYBLOCKS_192.append(BLOCK3)
elif efuse.category == "calibration" and efuse.block == 3:
self.ADC_CALIBRATION.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.name in ["CUSTOM_MAC_CRC", "CUSTOM_MAC"]:
self.CUSTOM_MAC.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "spi pad":
efuse.class_type = "spipin"
f = Field()
f.name = "WAFER_VERSION_MAJOR"
f.block = 0
f.bit_len = 3
f.type = f"uint:{f.bit_len}"
f.category = "identity"
f.class_type = "wafer"
f.description = "calc WAFER VERSION MAJOR from CHIP_VER_REV1 and CHIP_VER_REV2 and apb_ctl_date (read only)"
self.CALC.append(f)
f = Field()
f.name = "PKG_VERSION"
f.block = 0
f.bit_len = 4
f.type = f"uint:{f.bit_len}"
f.category = "identity"
f.class_type = "pkg"
f.description = (
"calc Chip package = CHIP_PACKAGE_4BIT << 3 + CHIP_PACKAGE (read only)"
)
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,365 @@
# This file includes the operations with eFuses for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
p = subparsers.add_parser(
"burn_key",
help="Burn a 256-bit key to EFUSE: %s" % ", ".join(efuses.BLOCKS_FOR_KEYS),
)
p.add_argument(
"--no-protect-key",
help="Disable default read- and write-protecting of the key. "
"If this option is not set, once the key is flashed "
"it cannot be read back or changed.",
action="store_true",
)
add_force_write_always(p)
add_show_sensitive_info_option(p)
p.add_argument(
"block",
help='Key block to burn. "flash_encryption" (block1), '
'"secure_boot_v1" (block2), "secure_boot_v2" (block2)',
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
p.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
for _ in efuses.BLOCKS_FOR_KEYS:
p.add_argument(
"block",
help='Key block to burn. "flash_encryption" (block1), '
'"secure_boot_v1" (block2), "secure_boot_v2" (block2)',
metavar="BLOCK",
nargs="?",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
p.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
metavar="KEYFILE",
nargs="?",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest "
"to eFuse for use with Secure Boot V2",
)
burn_key_digest.add_argument(
"keyfile", help="Key file to digest (PEM format)", type=argparse.FileType("rb")
)
burn_key_digest.add_argument(
"--no-protect-key",
help="Disable default write-protecting of the key digest. "
"If this option is not set, once the key is flashed it cannot be changed.",
action="store_true",
)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. This means GPIO12 can be high or low at reset "
"without changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format "
"with bytes separated by colons "
"(e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
# Writing to BLK3:
# - MAC_VERSION = 1
# - CUSTOM_MAC = AA:CD:EF:01:02:03
# - CUSTOM_MAC_CRC = crc8(CUSTOM_MAC)
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
version = efuses["MAC_VERSION"].get()
if version > 0:
print(
"Custom MAC Address version {}: {}".format(
version, efuses["CUSTOM_MAC"].get()
)
)
else:
print("Custom MAC Address is not set in the device.")
def set_flash_voltage(esp, efuses, args):
sdio_force = efuses["XPD_SDIO_FORCE"]
sdio_tieh = efuses["XPD_SDIO_TIEH"]
sdio_reg = efuses["XPD_SDIO_REG"]
# check efuses aren't burned in a way which makes this impossible
if args.voltage == "OFF" and sdio_reg.get() != 0:
raise esptool.FatalError(
"Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned"
)
if args.voltage == "1.8V" and sdio_tieh.get() != 0:
raise esptool.FatalError(
"Can't set regulator to 1.8V is XPD_SDIO_TIEH efuse is already burned"
)
if args.voltage == "OFF":
msg = "Disable internal flash voltage regulator (VDD_SDIO). "
"SPI flash will need to be powered from an external source.\n"
"The following efuse is burned: XPD_SDIO_FORCE.\n"
"It is possible to later re-enable the internal regulator (%s) " % (
"to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V"
)
"by burning an additional efuse"
elif args.voltage == "1.8V":
msg = "Set internal flash voltage regulator (VDD_SDIO) to 1.8V.\n"
"The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG.\n"
"It is possible to later increase the voltage to 3.3V (permanently) "
"by burning additional efuse XPD_SDIO_TIEH"
elif args.voltage == "3.3V":
msg = "Enable internal flash voltage regulator (VDD_SDIO) to 3.3V.\n"
"The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG, XPD_SDIO_TIEH."
print(msg)
sdio_force.save(1) # Disable GPIO12
if args.voltage != "OFF":
sdio_reg.save(1) # Enable internal regulator
if args.voltage == "3.3V":
sdio_tieh.save(1)
print("VDD_SDIO setting complete.")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def adc_info(esp, efuses, args):
adc_vref = efuses["ADC_VREF"]
blk3_reserve = efuses["BLK3_PART_RESERVE"]
vref_raw = adc_vref.get_raw()
if vref_raw == 0:
print("ADC VRef calibration: None (1100mV nominal)")
else:
print("ADC VRef calibration: %dmV" % adc_vref.get())
if blk3_reserve.get():
print("ADC readings stored in efuse BLOCK3:")
print(" ADC1 Low reading (150mV): %d" % efuses["ADC1_TP_LOW"].get())
print(" ADC1 High reading (850mV): %d" % efuses["ADC1_TP_HIGH"].get())
print(" ADC2 Low reading (150mV): %d" % efuses["ADC2_TP_LOW"].get())
print(" ADC2 High reading (850mV): %d" % efuses["ADC2_TP_HIGH"].get())
def burn_key(esp, efuses, args):
datafile_list = args.keyfile[
0 : len([keyfile for keyfile in args.keyfile if keyfile is not None]) :
]
block_name_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
efuses.force_write_always = args.force_write_always
no_protect_key = args.no_protect_key
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list):
raise esptool.FatalError(
"The number of blocks (%d) and datafile (%d) should be the same."
% (len(block_name_list), len(datafile_list))
)
print("Burn keys to blocks:")
for block_name, datafile in zip(block_name_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
data = datafile.read()
revers_msg = None
if block_name in ("flash_encryption", "secure_boot_v1"):
revers_msg = "\tReversing the byte order"
data = data[::-1]
print(" - %s" % (efuse.name), end=" ")
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. "
"Key file must be %d bytes (%d bits) of raw binary key data."
% (len(data), num_bytes, num_bytes * 8)
)
efuse.save(data)
if block_name in ("flash_encryption", "secure_boot_v1"):
if not no_protect_key:
print("\tDisabling read to key block")
efuse.disable_read()
if not no_protect_key:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if args.no_protect_key:
print("Key is left unprotected as per --no-protect-key argument.")
msg = "Burn keys in efuse blocks.\n"
if no_protect_key:
msg += (
"The key block will left readable and writeable (due to --no-protect-key)"
)
else:
msg += "The key block will be read and write protected "
"(no further changes or readback)"
print(msg, "\n")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
if efuses.coding_scheme == efuses.REGS.CODING_SCHEME_34:
raise esptool.FatalError("burn_key_digest only works with 'None' coding scheme")
chip_revision = esp.get_chip_revision()
if chip_revision < 300:
raise esptool.FatalError(
"Incorrect chip revision for Secure boot v2. "
"Detected: v%d.%d. Expected: >= v3.0"
% (chip_revision / 100, chip_revision % 100)
)
digest = espsecure._digest_sbv2_public_key(args.keyfile)
efuse = efuses["BLOCK2"]
num_bytes = efuse.bit_len // 8
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. "
"Digest must be %d bytes (%d bits) of raw binary key data."
% (len(digest), num_bytes, num_bytes * 8)
)
print(" - %s" % (efuse.name), end=" ")
print(
"-> [{}]".format(
util.hexify(digest, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(digest))
)
)
efuse.save(digest)
if not args.no_protect_key:
print("Disabling write to efuse %s..." % (efuse.name))
efuse.disable_write()
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,139 @@
# This file describes eFuses controller for ESP32-C2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
from bitstring import BitStream
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-C2"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 1
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 40 # MHz
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data
def check_rd_protection_area(self):
# checks fields which have the read protection bits.
# if the read protection bit is set then we need to reset this field to 0.
def get_read_disable_mask(blk):
mask = 0
if isinstance(blk.read_disable_bit, list):
for i in blk.read_disable_bit:
mask |= 1 << i
else:
mask = 1 << blk.read_disable_bit
return mask
read_disable_bit = self.read_field("RD_DIS", bitstring=False)
for b in self.Blocks.BLOCKS:
blk = self.Blocks.get(b)
block = self.read_block(blk.id)
if (
blk.read_disable_bit is not None
and read_disable_bit & get_read_disable_mask(blk)
):
if isinstance(blk.read_disable_bit, list):
if read_disable_bit & (1 << blk.read_disable_bit[0]):
block.set(
0, [i for i in range(blk.len * 32 // 2, blk.len * 32)]
)
if read_disable_bit & (1 << blk.read_disable_bit[1]):
block.set(0, [i for i in range(0, blk.len * 32 // 2)])
else:
block.set(0)
else:
for field in self.Fields.EFUSES:
if (
blk.id == field.block
and field.read_disable_bit is not None
and read_disable_bit & get_read_disable_mask(field)
):
raw_data = self.read_field(field.name)
raw_data.set(0)
block.pos = block.length - (
field.word * 32 + field.pos + raw_data.length
)
block.overwrite(BitStream(raw_data.length))
self.overwrite_mem_from_block(blk, block)

View File

@@ -0,0 +1,382 @@
# This file describes eFuses for ESP32-C2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-C2":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-C2 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MINOR"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
xtal_freq = self.get_crystal_freq()
if xtal_freq not in [26, 40]:
raise esptool.FatalError(
"The eFuse supports only xtal=26M and 40M (xtal was %d)" % xtal_freq
)
self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF)
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
)
tpgm_inactive_val = 200 if xtal_freq == 40 else 130
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF0_REG,
self.REGS.EFUSE_TPGM_INACTIVE_M,
tpgm_inactive_val,
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR_REG + offs * 4)
for offs in range(1)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
if new_value_str.count(":") != 5:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "")
if len(hexad) != 12:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal number "
"(12 hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
raise esptool.FatalError("Writing Factory MAC address is not supported")
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
# fmt: off
("USER", 0, None), # User purposes (software-only use)
("XTS_AES_128_KEY", 1, None), # (whole 256bits) flash/PSRAM encryption
("XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS", 2, None), # (lo 128bits) flash/PSRAM encryption
("SECURE_BOOT_DIGEST", 3, "DIGEST"),
# (hi 128bits) Secure Boot key digest
# fmt: on
]
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]

View File

@@ -0,0 +1,147 @@
# This file describes eFuses fields and registers for ESP32-C2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import copy
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x60008800
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_PGM_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x88
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x8C
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x90
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x94
EFUSE_RD_REPEAT_ERR_REG = DR_REG_EFUSE_BASE + 0x80
EFUSE_RD_RS_ERR_REG = DR_REG_EFUSE_BASE + 0x84
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR_REG, 0x7, 0, 3), # BLOCK1
(EFUSE_RD_RS_ERR_REG, 0x7, 4, 7), # BLOCK2
(EFUSE_RD_RS_ERR_REG, 0x7, 8, 11), # BLOCK3
]
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x118
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
EFUSE_WR_TIM_CONF0_REG = DR_REG_EFUSE_BASE + 0x110
EFUSE_TPGM_INACTIVE_S = 8
EFUSE_TPGM_INACTIVE_M = 0xFF << EFUSE_TPGM_INACTIVE_S
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x114
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x108
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", ["BLOCK0"], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 2, None),
("BLOCK1", ["BLOCK1"], 1, __base_rd_regs + 0x034, __base_wr_regs, 5, None, 3, None),
("BLOCK2", ["BLOCK2"], 2, __base_rd_regs + 0x040, __base_wr_regs, 6, None, 8, None),
("BLOCK_KEY0", ["BLOCK3"], 3, __base_rd_regs + 0x060, __base_wr_regs, 7, [0, 1], 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
def get_blocks_for_keys(self):
return ["BLOCK_KEY0"]
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MINOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in ["BLOCK_KEY0"]:
self.KEYBLOCKS.append(efuse)
BLOCK_KEY0_LOW_128 = copy.deepcopy(efuse)
BLOCK_KEY0_LOW_128.name = "BLOCK_KEY0_LOW_128"
BLOCK_KEY0_LOW_128.type = "bytes:16"
BLOCK_KEY0_LOW_128.bit_len = 16 * 8
BLOCK_KEY0_LOW_128.description = (
"BLOCK_KEY0 - lower 128-bits. 128-bit key of Flash Encryption"
)
BLOCK_KEY0_LOW_128.read_disable_bit = efuse.read_disable_bit[0]
self.KEYBLOCKS.append(BLOCK_KEY0_LOW_128)
BLOCK_KEY0_HI_128 = copy.deepcopy(efuse)
BLOCK_KEY0_HI_128.name = "BLOCK_KEY0_HI_128"
BLOCK_KEY0_HI_128.word = 4
BLOCK_KEY0_HI_128.type = "bytes:16"
BLOCK_KEY0_HI_128.bit_len = 16 * 8
BLOCK_KEY0_HI_128.description = (
"BLOCK_KEY0 - higher 128-bits. 128-bits key of Secure Boot"
)
BLOCK_KEY0_HI_128.read_disable_bit = efuse.read_disable_bit[1]
self.KEYBLOCKS.append(BLOCK_KEY0_HI_128)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,356 @@
# This file includes the operations with eFuses for ESP32-C2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support "
"post-write data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software.",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 128/256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in range(1):
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 128/256 bits of binary key data",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse an ECDSA public key and burn the digest "
"to higher 128-bits of BLOCK_KEY0",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"keyfile", help="Key file to digest (PEM format)", type=argparse.FileType("rb")
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low "
"at reset without changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK1."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format "
"with bytes separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
efuses["CUSTOM_MAC_USED"].save(1)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MINOR"].get() == 1:
print(" RF_REF_I_BIAS_CONFIG: {}".format(efuses["RF_REF_I_BIAS_CONFIG"].get()))
print(" LDO_VOL_BIAS_CONFIG_LOW: {}".format(efuses["LDO_VOL_BIAS_CONFIG_LOW"].get()))
print(" LDO_VOL_BIAS_CONFIG_HIGH: {}".format(efuses["LDO_VOL_BIAS_CONFIG_HIGH"].get()))
print(" PVT_LOW: {}".format(efuses["PVT_LOW"].get()))
print(" PVT_HIGH: {}".format(efuses["PVT_HIGH"].get()))
print(" ADC_CALIBRATION_0: {}".format(efuses["ADC_CALIBRATION_0"].get()))
print(" ADC_CALIBRATION_1: {}".format(efuses["ADC_CALIBRATION_1"].get()))
print(" ADC_CALIBRATION_2: {}".format(efuses["ADC_CALIBRATION_2"].get()))
else:
print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
# fmt: on
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
util.check_duplicate_name_in_list(keypurpose_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and "
"keypurpose (%d) should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
assert 1 <= len(block_name_list) <= 2, "Unexpected case"
if len(block_name_list) == 2:
incompatible = True if "XTS_AES_128_KEY" in keypurpose_list else False
permitted_purposes = [
"XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS",
"SECURE_BOOT_DIGEST",
]
incompatible |= (
keypurpose_list[0] in permitted_purposes
and keypurpose_list[1] not in permitted_purposes
)
if incompatible:
raise esptool.FatalError(
"These keypurposes are incompatible %s" % (keypurpose_list)
)
print("Burn keys to blocks:")
for datafile, keypurpose in zip(datafile_list, keypurpose_list):
data = datafile if isinstance(datafile, bytes) else datafile.read()
if keypurpose == "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS":
efuse = efuses["BLOCK_KEY0_LOW_128"]
elif keypurpose == "SECURE_BOOT_DIGEST":
efuse = efuses["BLOCK_KEY0_HI_128"]
if len(data) == 32:
print(
"\tProgramming only left-most 128-bits from SHA256 hash of "
"public key to highest 128-bits of BLOCK KEY0"
)
data = data[:16]
elif len(data) != efuse.bit_len // 8:
raise esptool.FatalError(
"Wrong length of this file for SECURE_BOOT_DIGEST. "
"Got %d (expected %d or %d)" % (len(data), 32, efuse.bit_len // 8)
)
assert len(data) == 16, "Only 16 bytes expected"
else:
efuse = efuses["BLOCK_KEY0"]
num_bytes = efuse.bit_len // 8
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if keypurpose.startswith("XTS_AES_"):
revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. "
"Key file must be %d bytes (%d bits) of raw binary key data."
% (len(data), num_bytes, num_bytes * 8)
)
if keypurpose.startswith("XTS_AES_"):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage
# of checking it as the whole field.
efuse.save(data)
if keypurpose == "XTS_AES_128_KEY":
if efuses["XTS_KEY_LENGTH_256"].get():
print("\t'XTS_KEY_LENGTH_256' is already '1'")
else:
print("\tXTS_KEY_LENGTH_256 -> 1")
efuses["XTS_KEY_LENGTH_256"].save(1)
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
datafile = args.keyfile
args.keypurpose = ["SECURE_BOOT_DIGEST"]
args.block = ["BLOCK_KEY0"]
digest = espsecure._digest_sbv2_public_key(datafile)
digest = digest[:16]
num_bytes = efuses["BLOCK_KEY0_HI_128"].bit_len // 8
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. "
"Digest must be %d bytes (%d bits) of raw binary key data."
% (len(digest), num_bytes, num_bytes * 8)
)
burn_key(esp, efuses, args, digest=[digest])
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-C3 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-C3"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
def get_minor_chip_version(self):
return 4
def get_crystal_freq(self):
return 40 # MHz (common for all chips)
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,447 @@
# This file describes eFuses for ESP32-C3 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-C3":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-C3 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MAJOR"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
if apb_freq != 40:
raise esptool.FatalError(
"The eFuse supports only xtal=40M (xtal was %d)" % apb_freq
)
self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF)
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg_f, fail_bit = self.REGS.BLOCK_FAIL_BIT[block.id]
if fail_bit is None:
block.fail = False
else:
block.fail = self.read_reg(addr_reg_f) & (1 << fail_bit) != 0
addr_reg_n, num_mask, num_offs = self.REGS.BLOCK_NUM_ERRORS[block.id]
if num_mask is None or num_offs is None:
block.num_errors = 0
else:
block.num_errors = (
self.read_reg(addr_reg_n) >> num_offs
) & num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1
lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
if new_value_str.count(":") != 5:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "")
if len(hexad) != 12:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal number "
"(12 hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError("Writing Factory MAC address is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
for key in self.KEY_PURPOSES:
if key[1] == raw_val:
return key[0]
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
str_new_value = self.get_name(raw_val)
if self.name == "KEY_PURPOSE_5" and str_new_value.startswith("XTS_AES"):
raise esptool.FatalError(f"{self.name} can not have {str_new_value} key due to a hardware bug (please see TRM for more details)")
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,189 @@
# This file describes eFuses fields and registers for ESP32-C3 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x60008800
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x1C0
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x1C4
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
# this chip has a design error so fail_bit is shifted by one block but err_num is in the correct place
BLOCK_FAIL_BIT = [
# error_reg, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 7), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 11), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 15), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 19), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 23), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 27), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 31), # BLOCK_KEY3
(EFUSE_RD_RS_ERR1_REG, 3), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 7), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, None), # BLOCK_SYS_DATA2
]
BLOCK_NUM_ERRORS = [
# error_reg, err_num_mask, err_num_offs
(EFUSE_RD_REPEAT_ERR0_REG, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4), # BLOCK_SYS_DATA2
]
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MAJOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
# It is not functional, a bug in the hardware
elif efuse.name == "JTAG_SEL_ENABLE":
self.ALL_EFUSES[i] = None
f = Field()
f.name = "WAFER_VERSION_MINOR"
f.block = 0
f.bit_len = 4
f.type = f"uint:{f.bit_len}"
f.category = "identity"
f.class_type = "wafer"
f.description = "calc WAFER VERSION MINOR = WAFER_VERSION_MINOR_HI << 3 + WAFER_VERSION_MINOR_LO (read only)"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,422 @@
# This file includes the operations with eFuses for ESP32-C3 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support "
"post-write data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] "
"will remain readable anyway. For the rest keypurposes the read-protection "
"will be defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. "
"This means GPIO45 can be high or low at reset without "
"changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MAJOR"].get() == 1:
print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get()))
print("")
print("ADC1 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC1_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC1_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC1_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC1_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC1_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC1_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get()))
print("")
print("ADC2 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC2_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC2_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC2_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC2_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC2_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC2_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get()))
else:
print("BLK_VERSION_MAJOR = {}".format(efuses["BLK_VERSION_MAJOR"].get_meaning()))
# fmt: on
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' "
"because write protection bit is set."
% (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) "
"of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-C6 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-C6"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 40 # MHz (common for all chips)
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,456 @@
# This file describes eFuses for ESP32-C6 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-C6":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-C6 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MINOR"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
if apb_freq != 40:
raise esptool.FatalError(
"The eFuse supports only xtal=40M (xtal was %d)" % apb_freq
)
self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF)
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1
lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
num_bytes = 8 if self.name == "MAC_EUI64" else 6
if new_value_str.count(":") != num_bytes - 1:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "").split(" ", 1)[0]
hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad
if len(hexad) != num_bytes * 2:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal number "
f"({num_bytes * 2} hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
if not self.is_field_calculated():
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
elif self.name == "MAC":
mac = self.get_raw(from_read)
elif self.name == "MAC_EUI64":
mac = self.parent["MAC"].get_bitstring(from_read).copy()
mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read)
mac.insert(mac_ext, 24)
mac = mac.bytes
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError(f"Burning {self.name} is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
for key in self.KEY_PURPOSES:
if key[1] == raw_val:
return key[0]
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
str_new_value = self.get_name(raw_val)
if self.name == "KEY_PURPOSE_5" and str_new_value.startswith("XTS_AES"):
raise esptool.FatalError(f"{self.name} can not have {str_new_value} key due to a hardware bug (please see TRM for more details)")
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,169 @@
# This file describes eFuses fields and registers for ESP32-C6 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x600B0800
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x1C0
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x1C4
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0, 3), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4, 7), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8, 11), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12, 15), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16, 19), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20, 23), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24, 27), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28, 31), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0, 3), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4, 7), # BLOCK_SYS_DATA2
]
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MINOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
f = Field()
f.name = "MAC_EUI64"
f.block = 1
f.bit_len = 64
f.type = f"bytes:{f.bit_len // 8}"
f.category = "MAC"
f.class_type = "mac"
f.description = "calc MAC_EUI64 = MAC[0]:MAC[1]:MAC[2]:MAC_EXT[0]:MAC_EXT[1]:MAC[3]:MAC[4]:MAC[5]"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,413 @@
# This file includes the operations with eFuses for ESP32-C6 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support "
"post-write data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] "
"will remain readable anyway. For the rest keypurposes the read-protection "
"will be defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. "
"This means GPIO45 can be high or low at reset without "
"changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MINOR"].get() == 1:
print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
print("")
print("ADC1 Calibration data stored in efuse BLOCK2:")
print(f"OCODE: {efuses['OCODE'].get()}")
print(f"INIT_CODE_ATTEN0: {efuses['ADC1_INIT_CODE_ATTEN0'].get()}")
print(f"INIT_CODE_ATTEN1: {efuses['ADC1_INIT_CODE_ATTEN1'].get()}")
print(f"INIT_CODE_ATTEN2: {efuses['ADC1_INIT_CODE_ATTEN2'].get()}")
print(f"INIT_CODE_ATTEN3: {efuses['ADC1_INIT_CODE_ATTEN3'].get()}")
print(f"CAL_VOL_ATTEN0: {efuses['ADC1_CAL_VOL_ATTEN0'].get()}")
print(f"CAL_VOL_ATTEN1: {efuses['ADC1_CAL_VOL_ATTEN1'].get()}")
print(f"CAL_VOL_ATTEN2: {efuses['ADC1_CAL_VOL_ATTEN2'].get()}")
print(f"CAL_VOL_ATTEN3: {efuses['ADC1_CAL_VOL_ATTEN3'].get()}")
print(f"INIT_CODE_ATTEN0_CH0: {efuses['ADC1_INIT_CODE_ATTEN0_CH0'].get()}")
print(f"INIT_CODE_ATTEN0_CH1: {efuses['ADC1_INIT_CODE_ATTEN0_CH1'].get()}")
print(f"INIT_CODE_ATTEN0_CH2: {efuses['ADC1_INIT_CODE_ATTEN0_CH2'].get()}")
print(f"INIT_CODE_ATTEN0_CH3: {efuses['ADC1_INIT_CODE_ATTEN0_CH3'].get()}")
print(f"INIT_CODE_ATTEN0_CH4: {efuses['ADC1_INIT_CODE_ATTEN0_CH4'].get()}")
print(f"INIT_CODE_ATTEN0_CH5: {efuses['ADC1_INIT_CODE_ATTEN0_CH5'].get()}")
print(f"INIT_CODE_ATTEN0_CH6: {efuses['ADC1_INIT_CODE_ATTEN0_CH6'].get()}")
else:
print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
# fmt: on
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' "
"because write protection bit is set."
% (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) "
"of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-H2"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 32 # MHz
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,458 @@
# This file describes eFuses for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-H2":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-H2 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MINOR"].get() == 2:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
# Based on `CONFIG_SOC_XTAL_SUPPORT_32M=y` for this target from ESP-IDF configuration
if apb_freq != 32:
raise esptool.FatalError(
"The eFuse supports only xtal=32M (xtal was %d)" % apb_freq
)
self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF)
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1
lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
num_bytes = 8 if self.name == "MAC_EUI64" else 6
if new_value_str.count(":") != num_bytes - 1:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "")
hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad
if len(hexad) != num_bytes * 2:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal number "
f"({num_bytes * 2} hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
if not self.is_field_calculated():
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
elif self.name == "MAC":
mac = self.get_raw(from_read)
elif self.name == "MAC_EUI64":
mac = self.parent["MAC"].get_bitstring(from_read).copy()
mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read)
mac.insert(mac_ext, 24)
mac = mac.bytes
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError(f"Burning {self.name} is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key
("RESERVED", 2, None, None, "no_need_rd_protect"), # Reserved
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
for key in self.KEY_PURPOSES:
if key[1] == raw_val:
return key[0]
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
str_new_value = self.get_name(raw_val)
if self.name == "KEY_PURPOSE_5" and str_new_value in ["XTS_AES_128_KEY", "ECDSA_KEY"]:
raise esptool.FatalError(f"{self.name} can not have {str_new_value} key due to a hardware bug (please see TRM for more details)")
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,169 @@
# This file describes eFuses fields and registers for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x600B0800
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x1C0
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x1C4
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0, 3), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4, 7), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8, 11), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12, 15), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16, 19), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20, 23), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24, 27), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28, 31), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0, 3), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4, 7), # BLOCK_SYS_DATA2
]
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MINOR is 2, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
f = Field()
f.name = "MAC_EUI64"
f.block = 1
f.bit_len = 64
f.type = f"bytes:{f.bit_len // 8}"
f.category = "MAC"
f.class_type = "mac"
f.description = "calc MAC_EUI64 = MAC[0]:MAC[1]:MAC[2]:MAC_EXT[0]:MAC_EXT[1]:MAC[3]:MAC[4]:MAC[5]"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,427 @@
# This file includes the operations with eFuses for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support "
"post-write data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] "
"will remain readable anyway. For the rest keypurposes the read-protection "
"will be defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low "
"at reset without changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MINOR"].get() == 2:
print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_CALIB"].get()))
print("")
print("ADC1 readings stored in efuse BLOCK2:")
print(" AVE_INITCODE_ATTEN0: {}".format(efuses["ADC1_AVE_INITCODE_ATTEN0"].get()))
print(" AVE_INITCODE_ATTEN1: {}".format(efuses["ADC1_AVE_INITCODE_ATTEN1"].get()))
print(" AVE_INITCODE_ATTEN2: {}".format(efuses["ADC1_AVE_INITCODE_ATTEN2"].get()))
print(" AVE_INITCODE_ATTEN3: {}".format(efuses["ADC1_AVE_INITCODE_ATTEN3"].get()))
print(" HI_DOUT_ATTEN0: {}".format(efuses["ADC1_HI_DOUT_ATTEN0"].get()))
print(" HI_DOUT_ATTEN1: {}".format(efuses["ADC1_HI_DOUT_ATTEN1"].get()))
print(" HI_DOUT_ATTEN2: {}".format(efuses["ADC1_HI_DOUT_ATTEN2"].get()))
print(" HI_DOUT_ATTEN3: {}".format(efuses["ADC1_HI_DOUT_ATTEN3"].get()))
print(" CH0_ATTEN0_INITCODE_DIFF: {}".format(efuses["ADC1_CH0_ATTEN0_INITCODE_DIFF"].get()))
print(" CH1_ATTEN0_INITCODE_DIFF: {}".format(efuses["ADC1_CH1_ATTEN0_INITCODE_DIFF"].get()))
print(" CH2_ATTEN0_INITCODE_DIFF: {}".format(efuses["ADC1_CH2_ATTEN0_INITCODE_DIFF"].get()))
print(" CH3_ATTEN0_INITCODE_DIFF: {}".format(efuses["ADC1_CH3_ATTEN0_INITCODE_DIFF"].get()))
print(" CH4_ATTEN0_INITCODE_DIFF: {}".format(efuses["ADC1_CH4_ATTEN0_INITCODE_DIFF"].get()))
else:
print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get()))
# fmt: on
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
if keypurpose == "ECDSA_KEY":
sk = espsecure.load_ecdsa_signing_key(datafile)
data = sk.to_string()
if len(data) == 24:
# the private key is 24 bytes long for NIST192p, add 8 bytes of padding
data = b"\x00" * 8 + data
else:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = f"\tReversing byte order for {keypurpose} hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' "
"because write protection bit is set."
% (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if keypurpose == "ECDSA_KEY":
if efuses["ECDSA_FORCE_USE_HARDWARE_K"].get() == 0:
# For ECDSA key purpose block permanently enable
# the hardware TRNG supplied k mode (most secure mode)
print("\tECDSA_FORCE_USE_HARDWARE_K: 0 -> 1")
efuses["ECDSA_FORCE_USE_HARDWARE_K"].save(1)
else:
print("\tECDSA_FORCE_USE_HARDWARE_K is already '1'")
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) "
"of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-H2(beta1)"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 32 # MHz (common for all chips)
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,458 @@
# This file describes eFuses for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-H2(beta1)":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-H2(beta1) chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MAJOR"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
if apb_freq != 32:
raise esptool.FatalError(
"The eFuse supports only xtal=32M (xtal was %d)" % apb_freq
)
self.update_reg(self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_NUM_M, 0xFF)
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG, self.REGS.EFUSE_DAC_CLK_DIV_M, 0x28
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_PWR_ON_NUM_M, 0x3000
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1
lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
num_bytes = 8 if self.name == "MAC_EUI64" else 6
if new_value_str.count(":") != num_bytes - 1:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "").split(" ", 1)[0]
hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad
if len(hexad) != num_bytes * 2:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal number "
f"({num_bytes * 2} hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
if not self.is_field_calculated():
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
elif self.name == "MAC":
mac = self.get_raw(from_read)
elif self.name == "MAC_EUI64":
mac = self.parent["MAC"].get_bitstring(from_read).copy()
mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read)
mac.insert(mac_ext, 24)
mac = mac.bytes
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError(f"Burning {self.name} is not supported")
raise esptool.FatalError("Writing Factory MAC address is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
for key in self.KEY_PURPOSES:
if key[1] == raw_val:
return key[0]
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
str_new_value = self.get_name(raw_val)
if self.name == "KEY_PURPOSE_5" and str_new_value.startswith("XTS_AES"):
raise esptool.FatalError(f"{self.name} can not have {str_new_value} key due to a hardware bug (please see TRM for more details)")
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,155 @@
# This file describes eFuses fields and registers for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x6001A000
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x1C0
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x1C4
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0, 3), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4, 7), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8, 11), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12, 15), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16, 19), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20, 23), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24, 27), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28, 31), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0, 3), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4, 7), # BLOCK_SYS_DATA2
]
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MAJOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
efuse_file = efuse_file.replace("esp32h2beta1", "esp32h2")
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,420 @@
# This file includes the operations with eFuses for ESP32-H2 chip
#
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support post-write "
"data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] will remain "
"readable anyway. For the rest keypurposes the read-protection will be "
"defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low "
"at reset without changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MAJOR"].get() == 1:
print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get()))
print("")
print("ADC1 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC1_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC1_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC1_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC1_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC1_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC1_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get()))
print("")
print("ADC2 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC2_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC2_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC2_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC2_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC2_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC2_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get()))
else:
print("BLK_VERSION_MAJOR = {}".format(efuses["BLK_VERSION_MAJOR"].get_meaning()))
# fmt: on
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' because write "
"protection bit is set." % (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw "
"binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-P4 chip
#
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-P4"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 0
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 40 # MHz (common for all chips)
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": 0,
"api_version": 0,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,435 @@
# This file describes eFuses for ESP32-P4 chip
#
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-P4":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-P4 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
# TODO add processing of self.Fields.BLOCK2_CALIBRATION_EFUSES
# if self["BLK_VERSION_MINOR"].get() == 1:
# self.efuses += [
# EfuseField.convert(self, efuse)
# for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
# ]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
if apb_freq != 40:
raise esptool.FatalError(
"The eFuse supports only xtal=40M (xtal was %d)" % apb_freq
)
# keep default timing settings
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
# TODO add support set_flash_voltage - "Flash voltage (VDD_SPI)"
return ""
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
num_bytes = 8 if self.name == "MAC_EUI64" else 6
if new_value_str.count(":") != num_bytes - 1:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "").split(" ", 1)[0]
hexad = hexad.split(" ", 1)[0] if self.is_field_calculated() else hexad
if len(hexad) != num_bytes * 2:
raise esptool.FatalError(
f"MAC Address needs to be a {num_bytes}-byte hexadecimal number "
f"({num_bytes * 2} hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
if not self.is_field_calculated():
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
elif self.name == "MAC":
mac = self.get_raw(from_read)
elif self.name == "MAC_EUI64":
mac = self.parent["MAC"].get_bitstring(from_read).copy()
mac_ext = self.parent["MAC_EXT"].get_bitstring(from_read)
mac.insert(mac_ext, 24)
mac = mac.bytes
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError(f"Burning {self.name} is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("ECDSA_KEY", 1, None, "Reverse", "need_rd_protect"), # ECDSA key
("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
("XTS_AES_256_KEY_2", 3, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_2 (flash/PSRAM encryption)
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
("KM_INIT_KEY", 12, None, None, "need_rd_protect"), # init key that is used for the generation of AES/ECDSA key
("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
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def get_name(self, raw_val):
for key in self.KEY_PURPOSES:
if key[1] == raw_val:
return key[0]
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,169 @@
# This file describes eFuses fields and registers for ESP32-P4 chip
#
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x5012D000
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x1C0
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x1C4
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0, 3), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4, 7), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8, 11), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12, 15), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16, 19), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20, 23), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24, 27), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28, 31), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0, 3), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4, 7), # BLOCK_SYS_DATA2
]
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0x0000FFFF << EFUSE_PWR_ON_NUM_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
# EFUSE_DAC_CONF_REG
EFUSE_DAC_NUM_S = 9
EFUSE_DAC_NUM_M = 0xFF << EFUSE_DAC_NUM_S
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MINOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
f = Field()
f.name = "MAC_EUI64"
f.block = 1
f.bit_len = 64
f.type = f"bytes:{f.bit_len // 8}"
f.category = "MAC"
f.class_type = "mac"
f.description = "calc MAC_EUI64 = MAC[0]:MAC[1]:MAC[2]:MAC_EXT[0]:MAC_EXT[1]:MAC[3]:MAC[4]:MAC[5]"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,458 @@
# This file includes the operations with eFuses for ESP32-P4 chip
#
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import io
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support "
"post-write data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] "
"will remain readable anyway. For the rest keypurposes the read-protection "
"will be defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data. For the ECDSA_KEY purpose use PEM file.",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. "
"This means GPIO45 can be high or low at reset without "
"changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
print("Not supported yet")
def get_custom_mac(esp, efuses, args):
print("Not supported yet")
def set_flash_voltage(esp, efuses, args):
raise esptool.FatalError("set_flash_voltage is not supported!")
def adc_info(esp, efuses, args):
print("not supported yet")
def key_block_is_unused(block, key_purpose_block):
if not block.is_readable() or not block.is_writeable():
return False
if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable():
return False
if not block.get_bitstring().all(False):
return False
return True
def get_next_key_block(efuses, current_key_block, block_name_list):
key_blocks = [b for b in efuses.blocks if b.key_purpose_name]
start = key_blocks.index(current_key_block)
# Sort key blocks so that we pick the next free block (and loop around if necessary)
key_blocks = key_blocks[start:] + key_blocks[0:start]
# Exclude any other blocks that will be be burned
key_blocks = [b for b in key_blocks if b.name not in block_name_list]
for block in key_blocks:
key_purpose_block = efuses[block.key_purpose_name]
if key_block_is_unused(block, key_purpose_block):
return block
return None
def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list):
i = keypurpose_list.index("XTS_AES_256_KEY")
block_name = block_name_list[i]
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
data = datafile_list[i].read()
if len(data) != 64:
raise esptool.FatalError(
"Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data)
)
key_block_2 = get_next_key_block(efuses, block, block_name_list)
if not key_block_2:
raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks")
keypurpose_list.append("XTS_AES_256_KEY_1")
datafile_list.append(io.BytesIO(data[:32]))
block_name_list.append(block_name)
keypurpose_list.append("XTS_AES_256_KEY_2")
datafile_list.append(io.BytesIO(data[32:]))
block_name_list.append(key_block_2.name)
keypurpose_list.pop(i)
datafile_list.pop(i)
block_name_list.pop(i)
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
if "XTS_AES_256_KEY" in keypurpose_list:
# XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
# XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list)
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
if keypurpose == "ECDSA_KEY":
sk = espsecure.load_ecdsa_signing_key(datafile)
data = sk.to_string()
if len(data) == 24:
# the private key is 24 bytes long for NIST192p, add 8 bytes of padding
data = b"\x00" * 8 + data
else:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = f"\tReversing byte order for {keypurpose} hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' "
"because write protection bit is set."
% (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) "
"of raw binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

View File

@@ -0,0 +1,92 @@
# This file describes eFuses controller for ESP32-S2 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalError
class EmulateEfuseController(EmulateEfuseControllerBase):
"""The class for virtual efuse operation. Using for HOST_TEST."""
CHIP_NAME = "ESP32-S2"
mem = None
debug = False
def __init__(self, efuse_file=None, debug=False):
self.Blocks = EfuseDefineBlocks
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
super(EmulateEfuseController, self).__init__(efuse_file, debug)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
""" esptool method start >>"""
def get_major_chip_version(self):
return 1
def get_minor_chip_version(self):
return 0
def get_crystal_freq(self):
return 40 # MHz (common for all chips)
def get_security_info(self):
return {
"flags": 0,
"flash_crypt_cnt": 0,
"key_purposes": 0,
"chip_id": None,
"api_version": None,
}
""" << esptool method end """
def handle_writing_event(self, addr, value):
if addr == self.REGS.EFUSE_CMD_REG:
if value & self.REGS.EFUSE_PGM_CMD:
self.copy_blocks_wr_regs_to_rd_regs(updated_block=(value >> 2) & 0xF)
self.clean_blocks_wr_regs()
self.check_rd_protection_area()
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
elif value == self.REGS.EFUSE_READ_CMD:
self.write_reg(addr, 0)
self.write_reg(self.REGS.EFUSE_CMD_REG, 0)
self.save_to_file()
def get_bitlen_of_block(self, blk, wr=False):
if blk.id == 0:
if wr:
return 32 * 8
else:
return 32 * blk.len
else:
if wr:
rs_coding = 32 * 3
return 32 * 8 + rs_coding
else:
return 32 * blk.len
def handle_coding_scheme(self, blk, data):
if blk.id != 0:
# CODING_SCHEME RS applied only for all blocks except BLK0.
coded_bytes = 12
data.pos = coded_bytes * 8
plain_data = data.readlist("32*uint:8")[::-1]
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(coded_bytes)
# 32 byte of data + 12 bytes RS
calc_encoded_data = list(rs.encode([x for x in plain_data]))
data.pos = 0
if calc_encoded_data != data.readlist("44*uint:8")[::-1]:
raise FatalError("Error in coding scheme data")
data = data[coded_bytes * 8 :]
if blk.len < 8:
data = data[(8 - blk.len) * 32 :]
return data

View File

@@ -0,0 +1,491 @@
# This file describes eFuses for ESP32S2 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import binascii
import struct
import sys
import time
from bitstring import BitArray
import esptool
import reedsolo
from .mem_definition import EfuseDefineBlocks, EfuseDefineFields, EfuseDefineRegisters
from .. import base_fields
from .. import util
class EfuseBlock(base_fields.EfuseBlockBase):
def len_of_burn_unit(self):
# The writing register window is 8 registers for any blocks.
# len in bytes
return 8 * 4
def __init__(self, parent, param, skip_read=False):
parent.read_coding_scheme()
super(EfuseBlock, self).__init__(parent, param, skip_read=skip_read)
def apply_coding_scheme(self):
data = self.get_raw(from_read=False)[::-1]
if len(data) < self.len_of_burn_unit():
add_empty_bytes = self.len_of_burn_unit() - len(data)
data = data + (b"\x00" * add_empty_bytes)
if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS:
# takes 32 bytes
# apply RS encoding
rs = reedsolo.RSCodec(12)
# 32 byte of data + 12 bytes RS
encoded_data = rs.encode([x for x in data])
words = struct.unpack("<" + "I" * 11, encoded_data)
# returns 11 words (8 words of data + 3 words of RS coding)
else:
# takes 32 bytes
words = struct.unpack("<" + ("I" * (len(data) // 4)), data)
# returns 8 words
return words
class EspEfuses(base_fields.EspEfusesBase):
"""
Wrapper object to manage the efuse fields in a connected ESP bootloader
"""
debug = False
do_not_confirm = False
def __init__(self, esp, skip_connect=False, debug=False, do_not_confirm=False):
self.Blocks = EfuseDefineBlocks()
self.Fields = EfuseDefineFields()
self.REGS = EfuseDefineRegisters
self.BURN_BLOCK_DATA_NAMES = self.Blocks.get_burn_block_data_names()
self.BLOCKS_FOR_KEYS = self.Blocks.get_blocks_for_keys()
self._esp = esp
self.debug = debug
self.do_not_confirm = do_not_confirm
if esp.CHIP_NAME != "ESP32-S2":
raise esptool.FatalError(
"Expected the 'esp' param for ESP32-S2 chip but got for '%s'."
% (esp.CHIP_NAME)
)
if not skip_connect:
flags = self._esp.get_security_info()["flags"]
GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE = 1 << 2
if flags & GET_SECURITY_INFO_FLAG_SECURE_DOWNLOAD_ENABLE:
raise esptool.FatalError(
"Secure Download Mode is enabled. The tool can not read eFuses."
)
self.blocks = [
EfuseBlock(self, self.Blocks.get(block), skip_read=skip_connect)
for block in self.Blocks.BLOCKS
]
if not skip_connect:
self.get_coding_scheme_warnings()
self.efuses = [EfuseField.convert(self, efuse) for efuse in self.Fields.EFUSES]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.KEYBLOCKS
]
if skip_connect:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
else:
if self["BLK_VERSION_MINOR"].get() == 1:
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
self.efuses += [
EfuseField.convert(self, efuse) for efuse in self.Fields.CALC
]
def __getitem__(self, efuse_name):
"""Return the efuse field with the given name"""
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
new_fields = False
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES:
if efuse.name == efuse_name or any(
x == efuse_name for x in efuse.alt_names
):
self.efuses += [
EfuseField.convert(self, efuse)
for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES
]
new_fields = True
if new_fields:
for e in self.efuses:
if efuse_name == e.name or any(x == efuse_name for x in e.alt_names):
return e
raise KeyError
def read_coding_scheme(self):
self.coding_scheme = self.REGS.CODING_SCHEME_RS
def print_status_regs(self):
print("")
self.blocks[0].print_block(self.blocks[0].err_bitarray, "err__regs", debug=True)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR0_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR0_REG)
)
)
print(
"{:27} 0x{:08x}".format(
"EFUSE_RD_RS_ERR1_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR1_REG)
)
)
def efuse_controller_setup(self):
self.set_efuse_timing()
self.clear_pgm_registers()
self.wait_efuse_idle()
def write_efuses(self, block):
self.efuse_program(block)
return self.get_coding_scheme_warnings(silent=True)
def clear_pgm_registers(self):
self.wait_efuse_idle()
for r in range(
self.REGS.EFUSE_PGM_DATA0_REG, self.REGS.EFUSE_PGM_DATA0_REG + 32, 4
):
self.write_reg(r, 0)
def wait_efuse_idle(self):
deadline = time.time() + self.REGS.EFUSE_BURN_TIMEOUT
while time.time() < deadline:
cmds = self.REGS.EFUSE_PGM_CMD | self.REGS.EFUSE_READ_CMD
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
if self.read_reg(self.REGS.EFUSE_CMD_REG) & cmds == 0:
# Due to a hardware error, we have to read READ_CMD again
# to make sure the efuse clock is normal.
# For PGM_CMD it is not necessary.
return
raise esptool.FatalError(
"Timed out waiting for Efuse controller command to complete"
)
def efuse_program(self, block):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_WRITE_OP_CODE)
self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_PGM_CMD | (block << 2))
self.wait_efuse_idle()
self.clear_pgm_registers()
self.efuse_read()
def efuse_read(self):
self.wait_efuse_idle()
self.write_reg(self.REGS.EFUSE_CONF_REG, self.REGS.EFUSE_READ_OP_CODE)
# need to add a delay after triggering EFUSE_READ_CMD, as ROM loader checks some
# efuse registers after each command is completed
# if ENABLE_SECURITY_DOWNLOAD or DIS_DOWNLOAD_MODE is enabled by the current cmd, then we need to try to reconnect to the chip.
try:
self.write_reg(
self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000
)
self.wait_efuse_idle()
except esptool.FatalError:
secure_download_mode_before = self._esp.secure_download_mode
try:
self._esp = self.reconnect_chip(self._esp)
except esptool.FatalError:
print("Can not re-connect to the chip")
if not self["DIS_DOWNLOAD_MODE"].get() and self[
"DIS_DOWNLOAD_MODE"
].get(from_read=False):
print(
"This is the correct behavior as we are actually burning "
"DIS_DOWNLOAD_MODE which disables the connection to the chip"
)
print("DIS_DOWNLOAD_MODE is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
print("Established a connection with the chip")
if self._esp.secure_download_mode and not secure_download_mode_before:
print("Secure download mode is enabled")
if not self["ENABLE_SECURITY_DOWNLOAD"].get() and self[
"ENABLE_SECURITY_DOWNLOAD"
].get(from_read=False):
print(
"espefuse tool can not continue to work in Secure download mode"
)
print("ENABLE_SECURITY_DOWNLOAD is enabled")
print("Successful")
sys.exit(0) # finish without errors
raise
def set_efuse_timing(self):
"""Set timing registers for burning efuses"""
# Configure clock
apb_freq = self.get_crystal_freq()
(
EFUSE_TSUP_A,
EFUSE_TPGM,
EFUSE_THP_A,
EFUSE_TPGM_INACTIVE,
) = self.REGS.EFUSE_PROGRAMMING_TIMING_PARAMETERS[apb_freq]
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG, self.REGS.EFUSE_TSUP_A_M, EFUSE_TSUP_A
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF0_REG, self.REGS.EFUSE_TPGM_M, EFUSE_TPGM
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF0_REG, self.REGS.EFUSE_THP_A_M, EFUSE_THP_A
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF0_REG,
self.REGS.EFUSE_TPGM_INACTIVE_M,
EFUSE_TPGM_INACTIVE,
)
(
EFUSE_DAC_CLK_DIV,
EFUSE_PWR_ON_NUM,
EFUSE_PWR_OFF_NUM,
) = self.REGS.VDDQ_TIMING_PARAMETERS[apb_freq]
self.update_reg(
self.REGS.EFUSE_DAC_CONF_REG,
self.REGS.EFUSE_DAC_CLK_DIV_M,
EFUSE_DAC_CLK_DIV,
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF1_REG,
self.REGS.EFUSE_PWR_ON_NUM_M,
EFUSE_PWR_ON_NUM,
)
self.update_reg(
self.REGS.EFUSE_WR_TIM_CONF2_REG,
self.REGS.EFUSE_PWR_OFF_NUM_M,
EFUSE_PWR_OFF_NUM,
)
EFUSE_TSUR_A, EFUSE_TRD, EFUSE_THR_A = self.REGS.EFUSE_READING_PARAMETERS[
apb_freq
]
# self.update_reg(
# self.REGS.EFUSE_RD_TIM_CONF_REG, self.REGS.EFUSE_TSUR_A_M, EFUSE_TSUR_A
# )
self.update_reg(
self.REGS.EFUSE_RD_TIM_CONF_REG, self.REGS.EFUSE_TRD_M, EFUSE_TRD
)
self.update_reg(
self.REGS.EFUSE_RD_TIM_CONF_REG, self.REGS.EFUSE_THR_A_M, EFUSE_THR_A
)
def get_coding_scheme_warnings(self, silent=False):
"""Check if the coding scheme has detected any errors."""
old_addr_reg = 0
reg_value = 0
ret_fail = False
for block in self.blocks:
if block.id == 0:
words = [
self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4)
for offs in range(5)
]
block.err_bitarray.pos = 0
for word in reversed(words):
block.err_bitarray.overwrite(BitArray("uint:32=%d" % word))
block.num_errors = block.err_bitarray.count(True)
block.fail = block.num_errors != 0
else:
addr_reg, err_num_mask, err_num_offs, fail_bit = self.REGS.BLOCK_ERRORS[
block.id
]
if err_num_mask is None or err_num_offs is None or fail_bit is None:
continue
if addr_reg != old_addr_reg:
old_addr_reg = addr_reg
reg_value = self.read_reg(addr_reg)
block.fail = reg_value & (1 << fail_bit) != 0
block.num_errors = (reg_value >> err_num_offs) & err_num_mask
ret_fail |= block.fail
if not silent and (block.fail or block.num_errors):
print(
"Error(s) in BLOCK%d [ERRORS:%d FAIL:%d]"
% (block.id, block.num_errors, block.fail)
)
if (self.debug or ret_fail) and not silent:
self.print_status_regs()
return ret_fail
def summary(self):
if self["VDD_SPI_FORCE"].get() == 0:
output = "Flash voltage (VDD_SPI) determined by GPIO45 on reset "
output += "(GPIO45=High: VDD_SPI pin is powered from internal 1.8V LDO\n"
output += "GPIO45=Low or NC: VDD_SPI pin is powered directly from "
output += "VDD3P3_RTC_IO via resistor Rspi. "
output += "Typically this voltage is 3.3 V)."
elif self["VDD_SPI_XPD"].get() == 0:
output = "Flash voltage (VDD_SPI) internal regulator disabled by efuse."
elif self["VDD_SPI_TIEH"].get() == 0:
output = "Flash voltage (VDD_SPI) set to 1.8V by efuse."
else:
output = "Flash voltage (VDD_SPI) set to 3.3V by efuse."
return output
class EfuseField(base_fields.EfuseFieldBase):
@staticmethod
def convert(parent, efuse):
return {
"mac": EfuseMacField,
"keypurpose": EfuseKeyPurposeField,
"t_sensor": EfuseTempSensor,
"adc_tp": EfuseAdcPointCalibration,
"wafer": EfuseWafer,
}.get(efuse.class_type, EfuseField)(parent, efuse)
class EfuseWafer(EfuseField):
def get(self, from_read=True):
hi_bits = self.parent["WAFER_VERSION_MINOR_HI"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_HI"].bit_len == 1
lo_bits = self.parent["WAFER_VERSION_MINOR_LO"].get(from_read)
assert self.parent["WAFER_VERSION_MINOR_LO"].bit_len == 3
return (hi_bits << 3) + lo_bits
def save(self, new_value):
raise esptool.FatalError("Burning %s is not supported" % self.name)
class EfuseTempSensor(EfuseField):
def get(self, from_read=True):
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * 0.1
class EfuseAdcPointCalibration(EfuseField):
def get(self, from_read=True):
STEP_SIZE = 4
value = self.get_bitstring(from_read)
sig = -1 if value[0] else 1
return sig * value[1:].uint * STEP_SIZE
class EfuseMacField(EfuseField):
def check_format(self, new_value_str):
if new_value_str is None:
raise esptool.FatalError(
"Required MAC Address in AA:CD:EF:01:02:03 format!"
)
if new_value_str.count(":") != 5:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal format "
"separated by colons (:)!"
)
hexad = new_value_str.replace(":", "")
if len(hexad) != 12:
raise esptool.FatalError(
"MAC Address needs to be a 6-byte hexadecimal number "
"(12 hexadecimal characters)!"
)
# order of bytearray = b'\xaa\xcd\xef\x01\x02\x03',
bindata = binascii.unhexlify(hexad)
# unicast address check according to
# https://tools.ietf.org/html/rfc7042#section-2.1
if esptool.util.byte(bindata, 0) & 0x01:
raise esptool.FatalError("Custom MAC must be a unicast MAC!")
return bindata
def check(self):
errs, fail = self.parent.get_block_errors(self.block)
if errs != 0 or fail:
output = "Block%d has ERRORS:%d FAIL:%d" % (self.block, errs, fail)
else:
output = "OK"
return "(" + output + ")"
def get(self, from_read=True):
if self.name == "CUSTOM_MAC":
mac = self.get_raw(from_read)[::-1]
else:
mac = self.get_raw(from_read)
return "%s %s" % (util.hexify(mac, ":"), self.check())
def save(self, new_value):
def print_field(e, new_value):
print(
" - '{}' ({}) {} -> {}".format(
e.name, e.description, e.get_bitstring(), new_value
)
)
if self.name == "CUSTOM_MAC":
bitarray_mac = self.convert_to_bitstring(new_value)
print_field(self, bitarray_mac)
super(EfuseMacField, self).save(new_value)
else:
# Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not possible,
# as it's written in the factory.
raise esptool.FatalError("Writing Factory MAC address is not supported")
# fmt: off
class EfuseKeyPurposeField(EfuseField):
KEY_PURPOSES = [
("USER", 0, None, None, "no_need_rd_protect"), # User purposes (software-only use)
("RESERVED", 1, None, None, "no_need_rd_protect"), # Reserved
("XTS_AES_256_KEY_1", 2, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_1 (flash/PSRAM encryption)
("XTS_AES_256_KEY_2", 3, None, "Reverse", "need_rd_protect"), # XTS_AES_256_KEY_2 (flash/PSRAM encryption)
("XTS_AES_128_KEY", 4, None, "Reverse", "need_rd_protect"), # XTS_AES_128_KEY (flash/PSRAM encryption)
("HMAC_DOWN_ALL", 5, None, None, "need_rd_protect"), # HMAC Downstream mode
("HMAC_DOWN_JTAG", 6, None, None, "need_rd_protect"), # JTAG soft enable key (uses HMAC Downstream mode)
("HMAC_DOWN_DIGITAL_SIGNATURE", 7, None, None, "need_rd_protect"), # Digital Signature peripheral key (uses HMAC Downstream mode)
("HMAC_UP", 8, None, None, "need_rd_protect"), # HMAC Upstream mode
("SECURE_BOOT_DIGEST0", 9, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST0 (Secure Boot key digest)
("SECURE_BOOT_DIGEST1", 10, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST1 (Secure Boot key digest)
("SECURE_BOOT_DIGEST2", 11, "DIGEST", None, "no_need_rd_protect"), # SECURE_BOOT_DIGEST2 (Secure Boot key digest)
("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
]
# fmt: on
KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES]
DIGEST_KEY_PURPOSES = [name[0] for name in KEY_PURPOSES if name[2] == "DIGEST"]
def check_format(self, new_value_str):
# str convert to int: "XTS_AES_128_KEY" - > str(4)
# if int: 4 -> str(4)
raw_val = new_value_str
for purpose_name in self.KEY_PURPOSES:
if purpose_name[0] == new_value_str:
raw_val = str(purpose_name[1])
break
if raw_val.isdigit():
if int(raw_val) not in [p[1] for p in self.KEY_PURPOSES if p[1] > 0]:
raise esptool.FatalError("'%s' can not be set (value out of range)" % raw_val)
else:
raise esptool.FatalError("'%s' unknown name" % raw_val)
return raw_val
def need_reverse(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[3] == "Reverse"
def need_rd_protect(self, new_key_purpose):
for key in self.KEY_PURPOSES:
if key[0] == new_key_purpose:
return key[4] == "need_rd_protect"
def get(self, from_read=True):
for p in self.KEY_PURPOSES:
if p[1] == self.get_raw(from_read):
return p[0]
return "FORBIDDEN_STATE"
def save(self, new_value):
raw_val = int(self.check_format(str(new_value)))
return super(EfuseKeyPurposeField, self).save(raw_val)

View File

@@ -0,0 +1,208 @@
# This file describes eFuses fields and registers for ESP32 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import yaml
from ..mem_definition_base import (
EfuseBlocksBase,
EfuseFieldsBase,
EfuseRegistersBase,
Field,
)
class EfuseDefineRegisters(EfuseRegistersBase):
EFUSE_MEM_SIZE = 0x01FC + 4
# EFUSE registers & command/conf values
DR_REG_EFUSE_BASE = 0x3F41A000
EFUSE_PGM_DATA0_REG = DR_REG_EFUSE_BASE
EFUSE_CHECK_VALUE0_REG = DR_REG_EFUSE_BASE + 0x020
EFUSE_CLK_REG = DR_REG_EFUSE_BASE + 0x1C8
EFUSE_CONF_REG = DR_REG_EFUSE_BASE + 0x1CC
EFUSE_STATUS_REG = DR_REG_EFUSE_BASE + 0x1D0
EFUSE_CMD_REG = DR_REG_EFUSE_BASE + 0x1D4
EFUSE_RD_RS_ERR0_REG = DR_REG_EFUSE_BASE + 0x194
EFUSE_RD_RS_ERR1_REG = DR_REG_EFUSE_BASE + 0x198
EFUSE_RD_REPEAT_ERR0_REG = DR_REG_EFUSE_BASE + 0x17C
EFUSE_RD_REPEAT_ERR1_REG = DR_REG_EFUSE_BASE + 0x180
EFUSE_RD_REPEAT_ERR2_REG = DR_REG_EFUSE_BASE + 0x184
EFUSE_RD_REPEAT_ERR3_REG = DR_REG_EFUSE_BASE + 0x188
EFUSE_RD_REPEAT_ERR4_REG = DR_REG_EFUSE_BASE + 0x18C
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_WR_TIM_CONF1_REG = DR_REG_EFUSE_BASE + 0x1F4
EFUSE_WR_TIM_CONF2_REG = DR_REG_EFUSE_BASE + 0x1F8
EFUSE_DATE_REG = DR_REG_EFUSE_BASE + 0x1FC
EFUSE_WRITE_OP_CODE = 0x5A5A
EFUSE_READ_OP_CODE = 0x5AA5
EFUSE_PGM_CMD_MASK = 0x3
EFUSE_PGM_CMD = 0x2
EFUSE_READ_CMD = 0x1
BLOCK_ERRORS = [
# error_reg, err_num_mask, err_num_offs, fail_bit
(EFUSE_RD_REPEAT_ERR0_REG, None, None, None), # BLOCK0
(EFUSE_RD_RS_ERR0_REG, 0x7, 0, 3), # MAC_SPI_8M_0
(EFUSE_RD_RS_ERR0_REG, 0x7, 4, 7), # BLOCK_SYS_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 8, 11), # BLOCK_USR_DATA
(EFUSE_RD_RS_ERR0_REG, 0x7, 12, 15), # BLOCK_KEY0
(EFUSE_RD_RS_ERR0_REG, 0x7, 16, 19), # BLOCK_KEY1
(EFUSE_RD_RS_ERR0_REG, 0x7, 20, 23), # BLOCK_KEY2
(EFUSE_RD_RS_ERR0_REG, 0x7, 24, 27), # BLOCK_KEY3
(EFUSE_RD_RS_ERR0_REG, 0x7, 28, 31), # BLOCK_KEY4
(EFUSE_RD_RS_ERR1_REG, 0x7, 0, 3), # BLOCK_KEY5
(EFUSE_RD_RS_ERR1_REG, 0x7, 4, 7), # BLOCK_SYS_DATA2
]
EFUSE_DAC_CONF_REG = DR_REG_EFUSE_BASE + 0x1E8
EFUSE_DAC_CLK_DIV_S = 0
EFUSE_DAC_CLK_DIV_M = 0xFF << EFUSE_DAC_CLK_DIV_S
EFUSE_RD_TIM_CONF_REG = DR_REG_EFUSE_BASE + 0x1EC
EFUSE_TSUR_A_S = 16
EFUSE_TSUR_A_M = 0xFF << EFUSE_TSUR_A_S
EFUSE_TRD_S = 8
EFUSE_TRD_M = 0xFF << EFUSE_TRD_S
EFUSE_THR_A_S = 0
EFUSE_THR_A_M = 0xFF << EFUSE_THR_A_S
EFUSE_WR_TIM_CONF0_REG = DR_REG_EFUSE_BASE + 0x1F0
EFUSE_TPGM_S = 16
EFUSE_TPGM_M = 0xFFFF << EFUSE_TPGM_S
EFUSE_TPGM_INACTIVE_S = 8
EFUSE_TPGM_INACTIVE_M = 0xFF << EFUSE_TPGM_INACTIVE_S
EFUSE_THP_A_S = 0
EFUSE_THP_A_M = 0xFF << EFUSE_THP_A_S
# EFUSE_WR_TIM_CONF1_REG
EFUSE_PWR_ON_NUM_S = 8
EFUSE_PWR_ON_NUM_M = 0xFFFF << EFUSE_PWR_ON_NUM_S
EFUSE_TSUP_A_S = 0
EFUSE_TSUP_A_M = 0xFF << EFUSE_TSUP_A_S
# EFUSE_WR_TIM_CONF2_REG
EFUSE_PWR_OFF_NUM_S = 0
EFUSE_PWR_OFF_NUM_M = 0xFFFF << EFUSE_PWR_OFF_NUM_S
# Configure clock
EFUSE_PROGRAMMING_TIMING_PARAMETERS = {
# APB Frequency: ( EFUSE_TSUP_A, EFUSE_TPGM, EFUSE_THP_A, EFUSE_TPGM_INACTIVE )
# Taken from TRM chapter "eFuse Controller": eFuse-Programming Timing
80: (0x2, 0x320, 0x2, 0x4),
40: (0x1, 0x190, 0x1, 0x2),
20: (0x1, 0xC8, 0x1, 0x1),
}
VDDQ_TIMING_PARAMETERS = {
# APB Frequency: ( EFUSE_DAC_CLK_DIV, EFUSE_PWR_ON_NUM, EFUSE_PWR_OFF_NUM )
# Taken from TRM chapter "eFuse Controller": eFuse VDDQ Timing Setting
80: (0xA0, 0xA200, 0x100),
40: (0x50, 0x5100, 0x80),
20: (0x28, 0x2880, 0x40),
}
EFUSE_READING_PARAMETERS = {
# APB Frequency: ( EFUSE_TSUR_A, EFUSE_TRD, EFUSE_THR_A )
# Taken from TRM chapter "eFuse Controller": eFuse-Read Timing
80: (0x2, 0x4, 0x2),
40: (0x1, 0x2, 0x1),
20: (0x1, 0x1, 0x1),
}
class EfuseDefineBlocks(EfuseBlocksBase):
__base_rd_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE
__base_wr_regs = EfuseDefineRegisters.EFUSE_PGM_DATA0_REG
# List of efuse blocks
# fmt: off
BLOCKS = [
# Name, Alias, Index, Read address, Write address, Write protect bit, Read protect bit, Len, key_purpose
("BLOCK0", [], 0, __base_rd_regs + 0x02C, __base_wr_regs, None, None, 6, None),
("MAC_SPI_8M_0", ["BLOCK1"], 1, __base_rd_regs + 0x044, __base_wr_regs, 20, None, 6, None),
("BLOCK_SYS_DATA", ["BLOCK2"], 2, __base_rd_regs + 0x05C, __base_wr_regs, 21, None, 8, None),
("BLOCK_USR_DATA", ["BLOCK3"], 3, __base_rd_regs + 0x07C, __base_wr_regs, 22, None, 8, None),
("BLOCK_KEY0", ["BLOCK4"], 4, __base_rd_regs + 0x09C, __base_wr_regs, 23, 0, 8, "KEY_PURPOSE_0"),
("BLOCK_KEY1", ["BLOCK5"], 5, __base_rd_regs + 0x0BC, __base_wr_regs, 24, 1, 8, "KEY_PURPOSE_1"),
("BLOCK_KEY2", ["BLOCK6"], 6, __base_rd_regs + 0x0DC, __base_wr_regs, 25, 2, 8, "KEY_PURPOSE_2"),
("BLOCK_KEY3", ["BLOCK7"], 7, __base_rd_regs + 0x0FC, __base_wr_regs, 26, 3, 8, "KEY_PURPOSE_3"),
("BLOCK_KEY4", ["BLOCK8"], 8, __base_rd_regs + 0x11C, __base_wr_regs, 27, 4, 8, "KEY_PURPOSE_4"),
("BLOCK_KEY5", ["BLOCK9"], 9, __base_rd_regs + 0x13C, __base_wr_regs, 28, 5, 8, "KEY_PURPOSE_5"),
("BLOCK_SYS_DATA2", ["BLOCK10"], 10, __base_rd_regs + 0x15C, __base_wr_regs, 29, 6, 8, None),
]
# fmt: on
def get_burn_block_data_names(self):
list_of_names = []
for block in self.BLOCKS:
blk = self.get(block)
if blk.name:
list_of_names.append(blk.name)
if blk.alias:
for alias in blk.alias:
list_of_names.append(alias)
return list_of_names
class EfuseDefineFields(EfuseFieldsBase):
def __init__(self) -> None:
# List of efuse fields from TRM the chapter eFuse Controller.
self.EFUSES = []
self.KEYBLOCKS = []
# if BLK_VERSION_MINOR is 1, these efuse fields are in BLOCK2
self.BLOCK2_CALIBRATION_EFUSES = []
self.CALC = []
dir_name = os.path.dirname(os.path.abspath(__file__))
dir_name, file_name = os.path.split(dir_name)
file_name = file_name + ".yaml"
dir_name, _ = os.path.split(dir_name)
efuse_file = os.path.join(dir_name, "efuse_defs", file_name)
with open(f"{efuse_file}", "r") as r_file:
e_desc = yaml.safe_load(r_file)
super().__init__(e_desc)
for i, efuse in enumerate(self.ALL_EFUSES):
if efuse.name in [
"BLOCK_USR_DATA",
"BLOCK_KEY0",
"BLOCK_KEY1",
"BLOCK_KEY2",
"BLOCK_KEY3",
"BLOCK_KEY4",
"BLOCK_KEY5",
"BLOCK_SYS_DATA2",
]:
if efuse.name == "BLOCK_USR_DATA":
efuse.bit_len = 256
efuse.type = "bytes:32"
self.KEYBLOCKS.append(efuse)
self.ALL_EFUSES[i] = None
elif efuse.category == "calibration":
self.BLOCK2_CALIBRATION_EFUSES.append(efuse)
self.ALL_EFUSES[i] = None
f = Field()
f.name = "WAFER_VERSION_MINOR"
f.block = 0
f.bit_len = 4
f.type = f"uint:{f.bit_len}"
f.category = "identity"
f.class_type = "wafer"
f.description = "calc WAFER VERSION MINOR = WAFER_VERSION_MINOR_HI << 3 + WAFER_VERSION_MINOR_LO (read only)"
self.CALC.append(f)
for efuse in self.ALL_EFUSES:
if efuse is not None:
self.EFUSES.append(efuse)
self.ALL_EFUSES = []

View File

@@ -0,0 +1,532 @@
# This file includes the operations with eFuses for ESP32S2 chip
#
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: GPL-2.0-or-later
import argparse
import io
import os # noqa: F401. It is used in IDF scripts
import traceback
import espsecure
import esptool
from . import fields
from .. import util
from ..base_operations import (
add_common_commands,
add_force_write_always,
add_show_sensitive_info_option,
burn_bit,
burn_block_data,
burn_efuse,
check_error,
dump,
read_protect_efuse,
summary,
write_protect_efuse,
)
def protect_options(p):
p.add_argument(
"--no-write-protect",
help="Disable write-protecting of the key. The key remains writable. "
"(The keys use the RS coding scheme that does not support post-write "
"data changes. Forced write can damage RS encoding bits.) "
"The write-protecting of keypurposes does not depend on the option, "
"it will be set anyway.",
action="store_true",
)
p.add_argument(
"--no-read-protect",
help="Disable read-protecting of the key. The key remains readable software."
"The key with keypurpose[USER, RESERVED and *_DIGEST] "
"will remain readable anyway. For the rest keypurposes the read-protection "
"will be defined the option (Read-protect by default).",
action="store_true",
)
def add_commands(subparsers, efuses):
add_common_commands(subparsers, efuses)
burn_key = subparsers.add_parser(
"burn_key", help="Burn the key block with the specified name"
)
protect_options(burn_key)
add_force_write_always(burn_key)
add_show_sensitive_info_option(burn_key)
burn_key.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
action="append",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key.add_argument(
"keyfile",
help="File containing 256 bits of binary key data",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME,
)
burn_key_digest = subparsers.add_parser(
"burn_key_digest",
help="Parse a RSA public key and burn the digest to key efuse block",
)
protect_options(burn_key_digest)
add_force_write_always(burn_key_digest)
add_show_sensitive_info_option(burn_key_digest)
burn_key_digest.add_argument(
"block",
help="Key block to burn",
action="append",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
action="append",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
action="append",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
for _ in efuses.BLOCKS_FOR_KEYS:
burn_key_digest.add_argument(
"block",
help="Key block to burn",
nargs="?",
action="append",
metavar="BLOCK",
choices=efuses.BLOCKS_FOR_KEYS,
)
burn_key_digest.add_argument(
"keyfile",
help="Key file to digest (PEM format)",
nargs="?",
action="append",
metavar="KEYFILE",
type=argparse.FileType("rb"),
)
burn_key_digest.add_argument(
"keypurpose",
help="Purpose to set.",
nargs="?",
action="append",
metavar="KEYPURPOSE",
choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES,
)
p = subparsers.add_parser(
"set_flash_voltage",
help="Permanently set the internal flash voltage regulator "
"to either 1.8V, 3.3V or OFF. "
"This means GPIO45 can be high or low at reset without "
"changing the flash voltage.",
)
p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"])
p = subparsers.add_parser(
"burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3."
)
p.add_argument(
"mac",
help="Custom MAC Address to burn given in hexadecimal format with bytes "
"separated by colons (e.g. AA:CD:EF:01:02:03).",
type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"),
)
add_force_write_always(p)
p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.")
def burn_custom_mac(esp, efuses, args):
efuses["CUSTOM_MAC"].save(args.mac)
if not efuses.burn_all(check_batch_mode=True):
return
get_custom_mac(esp, efuses, args)
print("Successful")
def get_custom_mac(esp, efuses, args):
print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get()))
def set_flash_voltage(esp, efuses, args):
sdio_force = efuses["VDD_SPI_FORCE"]
sdio_tieh = efuses["VDD_SPI_TIEH"]
sdio_reg = efuses["VDD_SPI_XPD"]
# check efuses aren't burned in a way which makes this impossible
if args.voltage == "OFF" and sdio_reg.get() != 0:
raise esptool.FatalError(
"Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned"
)
if args.voltage == "1.8V" and sdio_tieh.get() != 0:
raise esptool.FatalError(
"Can't set regulator to 1.8V is VDD_SPI_TIEH efuse is already burned"
)
if args.voltage == "OFF":
msg = "Disable internal flash voltage regulator (VDD_SPI). SPI flash will "
"need to be powered from an external source.\n"
"The following efuse is burned: VDD_SPI_FORCE.\n"
"It is possible to later re-enable the internal regulator (%s) " % (
"to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V"
)
"by burning an additional efuse"
elif args.voltage == "1.8V":
msg = "Set internal flash voltage regulator (VDD_SPI) to 1.8V.\n"
"The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD.\n"
"It is possible to later increase the voltage to 3.3V (permanently) "
"by burning additional efuse VDD_SPI_TIEH"
elif args.voltage == "3.3V":
msg = "Enable internal flash voltage regulator (VDD_SPI) to 3.3V.\n"
"The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH."
print(msg)
sdio_force.save(1) # Disable GPIO45
if args.voltage != "OFF":
sdio_reg.save(1) # Enable internal regulator
if args.voltage == "3.3V":
sdio_tieh.save(1)
print("VDD_SPI setting complete.")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def adc_info(esp, efuses, args):
print("")
# fmt: off
if efuses["BLK_VERSION_MINOR"].get() == 1:
print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get()))
print("")
print("ADC1 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC1_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC1_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC1_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC1_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC1_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC1_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get()))
print("")
print("ADC2 readings stored in efuse BLOCK2:")
print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC2_MODE0_D1"].get()))
print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC2_MODE0_D2"].get()))
print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC2_MODE1_D1"].get()))
print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC2_MODE1_D2"].get()))
print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC2_MODE2_D1"].get()))
print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get()))
print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC2_MODE3_D1"].get()))
print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get()))
else:
print("BLK_VERSION_MINOR = {}".format(efuses["BLK_VERSION_MINOR"].get_meaning()))
# fmt: on
def key_block_is_unused(block, key_purpose_block):
if not block.is_readable() or not block.is_writeable():
return False
if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable():
return False
if not block.get_bitstring().all(False):
return False
return True
def get_next_key_block(efuses, current_key_block, block_name_list):
key_blocks = [b for b in efuses.blocks if b.key_purpose_name]
start = key_blocks.index(current_key_block)
# Sort key blocks so that we pick the next free block (and loop around if necessary)
key_blocks = key_blocks[start:] + key_blocks[0:start]
# Exclude any other blocks that will be be burned
key_blocks = [b for b in key_blocks if b.name not in block_name_list]
for block in key_blocks:
key_purpose_block = efuses[block.key_purpose_name]
if key_block_is_unused(block, key_purpose_block):
return block
return None
def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list):
i = keypurpose_list.index("XTS_AES_256_KEY")
block_name = block_name_list[i]
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
data = datafile_list[i].read()
if len(data) != 64:
raise esptool.FatalError(
"Incorrect key file size %d, XTS_AES_256_KEY should be 64 bytes" % len(data)
)
key_block_2 = get_next_key_block(efuses, block, block_name_list)
if not key_block_2:
raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks")
keypurpose_list.append("XTS_AES_256_KEY_1")
datafile_list.append(io.BytesIO(data[:32]))
block_name_list.append(block_name)
keypurpose_list.append("XTS_AES_256_KEY_2")
datafile_list.append(io.BytesIO(data[32:]))
block_name_list.append(key_block_2.name)
keypurpose_list.pop(i)
datafile_list.pop(i)
block_name_list.pop(i)
def burn_key(esp, efuses, args, digest=None):
if digest is None:
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
else:
datafile_list = digest[0 : len([name for name in digest if name is not None]) :]
efuses.force_write_always = args.force_write_always
block_name_list = args.block[
0 : len([name for name in args.block if name is not None]) :
]
keypurpose_list = args.keypurpose[
0 : len([name for name in args.keypurpose if name is not None]) :
]
if "XTS_AES_256_KEY" in keypurpose_list:
# XTS_AES_256_KEY is not an actual HW key purpose, needs to be split into
# XTS_AES_256_KEY_1 and XTS_AES_256_KEY_2
split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list)
util.check_duplicate_name_in_list(block_name_list)
if len(block_name_list) != len(datafile_list) or len(block_name_list) != len(
keypurpose_list
):
raise esptool.FatalError(
"The number of blocks (%d), datafile (%d) and keypurpose (%d) "
"should be the same."
% (len(block_name_list), len(datafile_list), len(keypurpose_list))
)
print("Burn keys to blocks:")
for block_name, datafile, keypurpose in zip(
block_name_list, datafile_list, keypurpose_list
):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
block_num = efuses.get_index_block_by_name(block_name)
block = efuses.blocks[block_num]
if digest is None:
data = datafile.read()
else:
data = datafile
print(" - %s" % (efuse.name), end=" ")
revers_msg = None
if efuses[block.key_purpose_name].need_reverse(keypurpose):
revers_msg = "\tReversing byte order for AES-XTS hardware peripheral"
data = data[::-1]
print(
"-> [{}]".format(
util.hexify(data, " ")
if args.show_sensitive_info
else " ".join(["??"] * len(data))
)
)
if revers_msg:
print(revers_msg)
if len(data) != num_bytes:
raise esptool.FatalError(
"Incorrect key file size %d. Key file must be %d bytes (%d bits) "
"of raw binary key data." % (len(data), num_bytes, num_bytes * 8)
)
if efuses[block.key_purpose_name].need_rd_protect(keypurpose):
read_protect = False if args.no_read_protect else True
else:
read_protect = False
write_protect = not args.no_write_protect
# using efuse instead of a block gives the advantage of
# checking it as the whole field.
efuse.save(data)
disable_wr_protect_key_purpose = False
if efuses[block.key_purpose_name].get() != keypurpose:
if efuses[block.key_purpose_name].is_writeable():
print(
"\t'%s': '%s' -> '%s'."
% (
block.key_purpose_name,
efuses[block.key_purpose_name].get(),
keypurpose,
)
)
efuses[block.key_purpose_name].save(keypurpose)
disable_wr_protect_key_purpose = True
else:
raise esptool.FatalError(
"It is not possible to change '%s' to '%s' because "
"write protection bit is set."
% (block.key_purpose_name, keypurpose)
)
else:
print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose))
if efuses[block.key_purpose_name].is_writeable():
disable_wr_protect_key_purpose = True
if disable_wr_protect_key_purpose:
print("\tDisabling write to '%s'." % block.key_purpose_name)
efuses[block.key_purpose_name].disable_write()
if read_protect:
print("\tDisabling read to key block")
efuse.disable_read()
if write_protect:
print("\tDisabling write to key block")
efuse.disable_write()
print("")
if not write_protect:
print("Keys will remain writeable (due to --no-write-protect)")
if args.no_read_protect:
print("Keys will remain readable (due to --no-read-protect)")
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")
def burn_key_digest(esp, efuses, args):
digest_list = []
datafile_list = args.keyfile[
0 : len([name for name in args.keyfile if name is not None]) :
]
block_list = args.block[
0 : len([block for block in args.block if block is not None]) :
]
for block_name, datafile in zip(block_list, datafile_list):
efuse = None
for block in efuses.blocks:
if block_name == block.name or block_name in block.alias:
efuse = efuses[block.name]
if efuse is None:
raise esptool.FatalError("Unknown block name - %s" % (block_name))
num_bytes = efuse.bit_len // 8
digest = espsecure._digest_sbv2_public_key(datafile)
if len(digest) != num_bytes:
raise esptool.FatalError(
"Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw "
"binary key data." % (len(digest), num_bytes, num_bytes * 8)
)
digest_list.append(digest)
burn_key(esp, efuses, args, digest=digest_list)
def espefuse(esp, efuses, args, command):
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest="operation")
add_commands(subparsers, efuses)
try:
cmd_line_args = parser.parse_args(command.split())
except SystemExit:
traceback.print_stack()
raise esptool.FatalError('"{}" - incorrect command'.format(command))
if cmd_line_args.operation == "execute_scripts":
configfiles = cmd_line_args.configfiles
index = cmd_line_args.index
# copy arguments from args to cmd_line_args
vars(cmd_line_args).update(vars(args))
if cmd_line_args.operation == "execute_scripts":
cmd_line_args.configfiles = configfiles
cmd_line_args.index = index
if cmd_line_args.operation is None:
parser.print_help()
parser.exit(1)
operation_func = globals()[cmd_line_args.operation]
# each 'operation' is a module-level function of the same name
operation_func(esp, efuses, cmd_line_args)
def execute_scripts(esp, efuses, args):
efuses.batch_mode_cnt += 1
del args.operation
scripts = args.scripts
del args.scripts
for file in scripts:
with open(file.name, "r") as file:
exec(compile(file.read(), file.name, "exec"))
if args.debug:
for block in efuses.blocks:
data = block.get_bitstring(from_read=False)
block.print_block(data, "regs_for_burn", args.debug)
efuses.batch_mode_cnt -= 1
if not efuses.burn_all(check_batch_mode=True):
return
print("Successful")

View File

@@ -0,0 +1,3 @@
from . import operations
from .emulate_efuse_controller import EmulateEfuseController
from .fields import EspEfuses

Some files were not shown because too many files have changed in this diff Show More