Add Code
This commit is contained in:
302
code/.venv/lib/python3.12/site-packages/espefuse/__init__.py
Normal file
302
code/.venv/lib/python3.12/site-packages/espefuse/__init__.py
Normal 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()
|
||||
@@ -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()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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"]
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 = []
|
||||
@@ -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")
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import operations
|
||||
from .emulate_efuse_controller import EmulateEfuseController
|
||||
from .fields import EspEfuses
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user