diff --git a/.github/workflows/test_esptool.yml b/.github/workflows/test_esptool.yml index 03ec9f0..4c84935 100644 --- a/.github/workflows/test_esptool.yml +++ b/.github/workflows/test_esptool.yml @@ -28,9 +28,6 @@ jobs: python test/test_merge_bin.py python test/test_modules.py - - name: Lint with flake8 - run: python -m flake8 - - name: Check the installed versions can run run: | esptool --help @@ -53,3 +50,17 @@ jobs: ./test/ci/setup_ci_build_env.sh make -C flasher_stub V=1 cd flasher_stub && python ./compare_stubs.py + + lint_esptool: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Lint with flake8 + run: | + pip install -e .[dev] + python -m flake8 + + - name: Check formatting with Black + uses: psf/black@stable diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f1e6723..d5e3b19 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,12 +79,16 @@ host_tests_2: - find . -mindepth 2 -type f -name ".coverage*" -print -exec mv --backup=numbered {} . \; check_python_style: - <<: *test_template + stage: test + image: $CI_DOCKER_REGISTRY/esp-idf-doc-env:v4.4-1-v4 # Has Python 3.6 (Python >= 3.6 needed for Black) + tags: + - host_test script: - # this step installs any 'dev' dependencies (ie flake8) that might be missing in the CI image. + # this step installs any 'dev' dependencies (ie flake8, Black) that are be missing in the CI image. # The runner should cache the downloads, so still quite fast. - - ./test/ci/multirun_with_pyenv.sh pip install --user -e .[dev] - - ./test/ci/multirun_with_pyenv.sh python -m flake8 + - pip install --user -e .[dev] + - python -m flake8 + - python -m black --check --diff . # Check all the scripts can run when installed under all Python versions check_installation_can_run: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..fe6991c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + - repo: https://github.com/psf/black + rev: 22.1.0 + hooks: + - id: black + language_version: python3.9 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 86e080c..1d39c5c 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,19 +1,19 @@ Contributions Guide =================== -We welcome contributions to the esptool project! +We welcome contributions to the ``esptool.py`` project! How to Contribute ----------------- -Contributions to esptool - fixing bugs, adding features, adding documentation - are welcome. We accept contributions via `Github Pull Requests `_. +Contributions to ``esptool.py`` - fixing bugs, adding features, adding documentation - are welcome. We accept contributions via `Github Pull Requests `_. .. _development-setup: Development Setup ----------------- -Development mode allows you to run the latest development version from the `esptool repository on GitHub `_. +Development mode allows you to run the latest development version from the `esptool.py repository on GitHub `_. .. code-block:: sh @@ -21,11 +21,11 @@ Development mode allows you to run the latest development version from the `espt $ cd esptool $ pip install --user -e . -This will install esptool’s dependencies and create some executable script wrappers in the user’s ``bin`` directory. The wrappers will run the scripts found in the git working directory directly, so any time the working directory contents change it will pick up the new versions. +This will install ``esptool.py``’s dependencies and create some executable script wrappers in the user’s ``bin`` directory. The wrappers will run the scripts found in the git working directory directly, so any time the working directory contents change it will pick up the new versions. It’s also possible to run the scripts directly from the working directory with this Development Mode installation. -To also install additional tools needed for actually developing and testing esptool, run this command to install a development copy of esptool *plus* packages useful for development: +To also install additional tools needed for actually developing and testing ``esptool.py``, run this command to install a development copy of ``esptool.py`` *plus* packages useful for development: :: @@ -36,7 +36,7 @@ To also install additional tools needed for actually developing and testing espt Reporting Issues ---------------- -Please report bugs in esptool if you find them. However, before reporting a bug please check through the following: +Please report bugs in ``esptool.py`` if you find them. However, before reporting a bug please check through the following: * `Troubleshooting Guide `_ - common problems and known issues. @@ -56,7 +56,7 @@ Before Contributing Before sending us a Pull Request, please consider this list of points: -* Have you tried running esptool test suite locally? +* Have you tried running ``esptool.py`` test suite locally? * Is the code adequately commented for people to understand how it is structured? @@ -71,14 +71,40 @@ Before sending us a Pull Request, please consider this list of points: Code Style & Static Analysis ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -esptool complies with Flake 8 and is valid Python 2 & Python 3 code (in the same source file.) +Please follow these coding standards when writing code for ``esptool.py``: -When you submit a Pull Request, the GitHub Actions automated build system will run automated checks for this, using the `flake8 tool `_. To check your code locally before submitting, run ``python -m flake8`` (the flake8 tool is installed as part of the development requirements shown at the beginning of this document.) +Pre-commit checks +""""""""""""""""" + +`pre-commit `_ is a framework for managing pre-commit hooks. These hooks help to identify simple issues before committing code for review. + +To use the tool, first install ``pre-commit`` and then the git hooks: + +:: + + $ python -m pip install pre-commit + $ pre-commit install + +On the first commit ``pre-commit`` will install the hooks, subsequent checks will be significantly faster. If an error is found an appropriate error message will be displayed. If the error was with ``black`` then the tool will fix them for you automatically. Review the changes and re-stage for commit if you are happy with them. + +Flake8 +"""""" + +``esptool.py`` complies with `flake8 `_ style guide enforcement. + +Black +""""" + +All files should be formatted using the `Black `_ auto-formatter. + +``Black`` and ``flake8`` tools will be automatically run by ``pre-commit`` if that is configured. To check your code manually before submitting, run ``python -m flake8`` and ``black .`` (the tools are installed as part of the development requirements shown at the beginning of this document). + +When you submit a Pull Request, the GitHub Actions automated build system will run automated checks using these tools. Automated Integration Tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The test directory contains an integration suite with some integration tests for ``esptool``: +The test directory contains an integration suite with some integration tests for ``esptool.py``: * ``test_imagegen.py`` tests the elf2image command and is run automatically by GitHub Actions for each Pull Request. You can run this command locally to check for regressions in the elf2image functionality. diff --git a/docs/conf_common.py b/docs/conf_common.py index 6d22fd9..88d605e 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -1,42 +1,45 @@ from esp_docs.conf_docs import * # noqa: F403,F401 -languages = ['en'] -idf_targets = ['esp8266', 'esp32', 'esp32s2', 'esp32s3', 'esp32c3'] +languages = ["en"] +idf_targets = ["esp8266", "esp32", "esp32s2", "esp32s3", "esp32c3"] # link roles config -github_repo = 'espressif/esptool' +github_repo = "espressif/esptool" # context used by sphinx_idf_theme -html_context['github_user'] = 'espressif' -html_context['github_repo'] = 'esptool' +html_context["github_user"] = "espressif" +html_context["github_repo"] = "esptool" -html_static_path = ['../_static'] +html_static_path = ["../_static"] # Conditional content -extensions += ['esp_docs.esp_extensions.dummy_build_system'] +extensions += ["esp_docs.esp_extensions.dummy_build_system"] ESP8266_DOCS = [] -ESP32_DOCS = ['espefuse/index.rst', - 'espsecure/index.rst', - ] +ESP32_DOCS = [ + "espefuse/index.rst", + "espsecure/index.rst", +] -ESP32S2_DOCS = ['espefuse/index.rst', - 'espsecure/index.rst', - ] +ESP32S2_DOCS = [ + "espefuse/index.rst", + "espsecure/index.rst", +] ESP32C3_DOCS = ESP32S2_DOCS ESP32S3_DOCS = ESP32S2_DOCS -conditional_include_dict = {'esp8266': ESP8266_DOCS, - 'esp32': ESP32_DOCS, - 'esp32s2': ESP32S2_DOCS, - 'esp32c3': ESP32C3_DOCS, - 'esp32s3': ESP32S3_DOCS, - } +conditional_include_dict = { + "esp8266": ESP8266_DOCS, + "esp32": ESP32_DOCS, + "esp32s2": ESP32S2_DOCS, + "esp32c3": ESP32C3_DOCS, + "esp32s3": ESP32S3_DOCS, +} # Extra options required by sphinx_idf_theme -project_slug = 'esptool' +project_slug = "esptool" -versions_url = './_static/esptool_versions.js' +versions_url = "./_static/esptool_versions.js" diff --git a/docs/en/conf.py b/docs/en/conf.py index c6fb26e..0362dfc 100644 --- a/docs/en/conf.py +++ b/docs/en/conf.py @@ -14,7 +14,8 @@ try: except ImportError: import os import sys - sys.path.insert(0, os.path.abspath('../')) + + sys.path.insert(0, os.path.abspath("../")) from conf_common import * # noqa: F403,F401 # General information about the project. @@ -25,4 +26,4 @@ copyright = "2016 - {}, Espressif Systems (Shanghai) Co., Ltd".format( # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -language = 'en' +language = "en" diff --git a/esp_rfc2217_server.py b/esp_rfc2217_server.py index f9146a0..d5ae841 100755 --- a/esp_rfc2217_server.py +++ b/esp_rfc2217_server.py @@ -7,14 +7,17 @@ # Redirect data from a TCP/IP connection to a serial port and vice versa using RFC 2217. # # This is a modified version of rfc2217_server.py provided by the pyserial package -# (https://pythonhosted.org/pyserial/examples.html#single-port-tcp-ip-serial-bridge-rfc-2217). -# It uses a custom PortManager for properly apply the RTS & DTR signals for reseting ESP chips. +# (pythonhosted.org/pyserial/examples.html#single-port-tcp-ip-serial-bridge-rfc-2217). +# It uses a custom PortManager to properly apply the RTS & DTR signals +# for reseting ESP chips. # -# Run the following command on the server side to make connection between /dev/ttyUSB1 and TCP port 4000: +# Run the following command on the server side to make +# connection between /dev/ttyUSB1 and TCP port 4000: # # python esp_rfc2217_server.py -p 4000 /dev/ttyUSB1 # -# Esptool can connect to the ESP device through that server as it is demonstrated in the following example: +# Esptool can connect to the ESP device through that server as it is +# demonstrated in the following example: # # esptool --port rfc2217://localhost:4000?ign_set_control flash_id # @@ -35,13 +38,21 @@ import time import serial import serial.rfc2217 -from serial.rfc2217 import (COM_PORT_OPTION, SET_CONTROL, SET_CONTROL_DTR_OFF, SET_CONTROL_DTR_ON, SET_CONTROL_RTS_OFF, - SET_CONTROL_RTS_ON) +from serial.rfc2217 import ( + COM_PORT_OPTION, + SET_CONTROL, + SET_CONTROL_DTR_OFF, + SET_CONTROL_DTR_ON, + SET_CONTROL_RTS_OFF, + SET_CONTROL_RTS_ON, +) class EspPortManager(serial.rfc2217.PortManager): - """ The beginning of the reset sequence is detected and the proper reset sequence is applied in a thread. The rest - of the reset sequence received is just ignored and not sent to the serial port. + """ + The beginning of the reset sequence is detected and the proper reset sequence + is applied in a thread. The rest of the reset sequence received is just ignored + and not sent to the serial port. """ def __init__(self, serial_port, connection, esp32r0_delay, logger=None): @@ -56,10 +67,14 @@ class EspPortManager(serial.rfc2217.PortManager): elif suboption[2:3] == SET_CONTROL_RTS_ON and not self.serial.dtr: reset_thread = threading.Thread(target=self._reset_thread) reset_thread.daemon = True - reset_thread.name = 'reset_thread' + reset_thread.name = "reset_thread" reset_thread.start() return - elif suboption[2:3] in [SET_CONTROL_DTR_ON, SET_CONTROL_RTS_ON, SET_CONTROL_RTS_OFF]: + elif suboption[2:3] in [ + SET_CONTROL_DTR_ON, + SET_CONTROL_RTS_ON, + SET_CONTROL_RTS_OFF, + ]: return # only in cases not handled above do the original implementation in PortManager super(EspPortManager, self)._telnet_process_subnegotiation(suboption) @@ -75,17 +90,18 @@ class EspPortManager(serial.rfc2217.PortManager): self.serial.setDTR(self.serial.dtr) def _reset_thread(self): - """ The reset logic is used from esptool because the RTS and DTR signals cannot be retransmitted through - RFC 2217 with proper timing. + """ + The reset logic is used from esptool because the RTS and DTR signals + cannot be retransmitted through RFC 2217 with proper timing. """ if self.logger: self.logger.info("Activating reset in thread") self._setDTR(False) # IO0=HIGH - self._setRTS(True) # EN=LOW, chip in reset + self._setRTS(True) # EN=LOW, chip in reset time.sleep(0.1) if self.esp32r0_delay: time.sleep(1.2) - self._setDTR(True) # IO0=LOW + self._setDTR(True) # IO0=LOW self._setRTS(False) # EN=HIGH, chip out of reset if self.esp32r0_delay: time.sleep(0.4) @@ -102,45 +118,46 @@ class Redirector(object): self.serial, self, esp32r0delay, - logger=logging.getLogger('rfc2217.server') if debug else None) - self.log = logging.getLogger('redirector') + logger=logging.getLogger("rfc2217.server") if debug else None, + ) + self.log = logging.getLogger("redirector") def statusline_poller(self): - self.log.debug('status line poll thread started') + self.log.debug("status line poll thread started") while self.alive: time.sleep(1) self.rfc2217.check_modem_lines() - self.log.debug('status line poll thread terminated') + self.log.debug("status line poll thread terminated") def shortcircuit(self): """connect the serial port to the TCP port by copying everything - from one side to the other""" + from one side to the other""" self.alive = True self.thread_read = threading.Thread(target=self.reader) self.thread_read.daemon = True - self.thread_read.name = 'serial->socket' + self.thread_read.name = "serial->socket" self.thread_read.start() self.thread_poll = threading.Thread(target=self.statusline_poller) self.thread_poll.daemon = True - self.thread_poll.name = 'status line poll' + self.thread_poll.name = "status line poll" self.thread_poll.start() self.writer() def reader(self): """loop forever and copy serial->socket""" - self.log.debug('reader thread started') + self.log.debug("reader thread started") while self.alive: try: data = self.serial.read(self.serial.in_waiting or 1) if data: # escape outgoing data when needed (Telnet IAC (0xff) character) - self.write(b''.join(self.rfc2217.escape(data))) + self.write(b"".join(self.rfc2217.escape(data))) except socket.error as msg: - self.log.error('{}'.format(msg)) + self.log.error("{}".format(msg)) # probably got disconnected break self.alive = False - self.log.debug('reader thread terminated') + self.log.debug("reader thread terminated") def write(self, data): """thread safe socket write with no data escaping. used to send telnet stuff""" @@ -154,71 +171,73 @@ class Redirector(object): data = self.socket.recv(1024) if not data: break - self.serial.write(b''.join(self.rfc2217.filter(data))) + self.serial.write(b"".join(self.rfc2217.filter(data))) except socket.error as msg: - self.log.error('{}'.format(msg)) + self.log.error("{}".format(msg)) # probably got disconnected break self.stop() def stop(self): """Stop copying""" - self.log.debug('stopping') + self.log.debug("stopping") if self.alive: self.alive = False self.thread_read.join() self.thread_poll.join() -if __name__ == '__main__': +if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description="RFC 2217 Serial to Network (TCP/IP) redirector.", - epilog="""\ -NOTE: no security measures are implemented. Anyone can remotely connect -to this service over the network. + epilog="NOTE: no security measures are implemented. " + "Anyone can remotely connect to this service over the network.\n" + "Only one connection at once is supported. " + "When the connection is terminated it waits for the next connect.", + ) -Only one connection at once is supported. When the connection is terminated -it waits for the next connect. -""") - - parser.add_argument('SERIALPORT') + parser.add_argument("SERIALPORT") parser.add_argument( - '-p', '--localport', + "-p", + "--localport", type=int, - help='local TCP port, default: %(default)s', - metavar='TCPPORT', - default=2217) + help="local TCP port, default: %(default)s", + metavar="TCPPORT", + default=2217, + ) parser.add_argument( - '-v', '--verbose', - dest='verbosity', - action='count', - help='print more diagnostic messages (option can be given multiple times)', - default=0) + "-v", + "--verbose", + dest="verbosity", + action="count", + help="print more diagnostic messages (option can be given multiple times)", + default=0, + ) parser.add_argument( - '--r0', + "--r0", help="Use delays necessary for ESP32 revision 0 chips", - action='store_true') + action="store_true", + ) args = parser.parse_args() if args.verbosity > 3: args.verbosity = 3 - level = (logging.WARNING, - logging.INFO, - logging.DEBUG, - logging.NOTSET)[args.verbosity] + level = (logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET)[ + args.verbosity + ] logging.basicConfig(level=logging.INFO) # logging.getLogger('root').setLevel(logging.INFO) - logging.getLogger('rfc2217').setLevel(level) + logging.getLogger("rfc2217").setLevel(level) # connect to serial port ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True) - ser.timeout = 3 # required so that the reader thread can exit + ser.timeout = 3 # required so that the reader thread can exit # reset control line as no _remote_ "terminal" has been connected yet ser.dtr = False ser.rts = False @@ -236,26 +255,22 @@ it waits for the next connect. srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - srv.bind(('', args.localport)) + srv.bind(("", args.localport)) srv.listen(1) logging.info("TCP/IP port: {}".format(args.localport)) while True: try: client_socket, addr = srv.accept() - logging.info('Connected by {}:{}'.format(addr[0], addr[1])) + logging.info("Connected by {}:{}".format(addr[0], addr[1])) client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) ser.rts = True ser.dtr = True # enter network <-> serial loop - r = Redirector( - ser, - client_socket, - args.verbosity > 0, - args.r0) + r = Redirector(ser, client_socket, args.verbosity > 0, args.r0) try: r.shortcircuit() finally: - logging.info('Disconnected') + logging.info("Disconnected") r.stop() client_socket.close() ser.dtr = False @@ -264,9 +279,9 @@ it waits for the next connect. # capable client) ser.apply_settings(settings) except KeyboardInterrupt: - sys.stdout.write('\n') + sys.stdout.write("\n") break except socket.error as msg: logging.error(str(msg)) - logging.info('--- exit ---') + logging.info("--- exit ---") diff --git a/espefuse.py b/espefuse.py index 1993c63..c58b38e 100644 --- a/espefuse.py +++ b/espefuse.py @@ -1,15 +1,18 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -# This executable script is a thin wrapper around the main functionality in the espefuse Python package +# This executable script is a thin wrapper around the main functionality +# in the espefuse Python package # -# If esptool (with espefuse and espsecure) is installed via setup.py or pip then this file is not used at all, -# it's compatibility for the older "run from source dir" esptool approach. +# If esptool (with espefuse and espsecure) is installed via setup.py or pip +# then this file is not used at all, +# it's compatibility for the older "run from source dir" espefuse approach. import espefuse -if __name__ == '__main__': +if __name__ == "__main__": espefuse._main() diff --git a/espefuse/__init__.py b/espefuse/__init__.py index 0f0f857..c9ad3dc 100755 --- a/espefuse/__init__.py +++ b/espefuse/__init__.py @@ -22,45 +22,66 @@ import espefuse.efuse.esp32s3beta2 as esp32s3beta2_efuse import esptool -DefChip = namedtuple('DefChip', ['chip_name', 'efuse_lib', 'chip_class']) +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', + "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', + "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), - '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), + "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), + "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()): +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 + 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: + 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) + 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 @@ -70,22 +91,30 @@ 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) + 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. + 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'] + 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: + [['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 = [] @@ -107,61 +136,107 @@ def main(custom_commandline=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']. + 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']. """ - init_parser = argparse.ArgumentParser(description='espefuse.py v%s - [ESP32xx] efuse get/set tool' % esptool.__version__, - prog='espefuse', add_help=False) + 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( + "--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( + "--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( + "--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( + "--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') + 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 == [] - 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) - efuses, efuse_operations = get_efuses(esp, just_print_help, debug_mode, common_args.do_not_confirm) + just_print_help = [ + True for arg in remaining_args if arg in ["--help", "-h"] + ] or remaining_args == [] + 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, + ) + 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 {command} -h for additional help') + subparsers = parser.add_subparsers( + dest="operation", help="Run espefuse {command} -h for additional help" + ) efuse_operations.add_commands(subparsers, efuses) grouped_remaining_args, used_cmds = split_on_groups(remaining_args) - print('espefuse.py v%s' % esptool.__version__) + print("espefuse.py v%s" % esptool.__version__) 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 + 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 @@ -170,7 +245,9 @@ def main(custom_commandline=None): if args.operation is None: parser.print_help() parser.exit(1) - assert len(unused_args) == 0, 'Not all commands were recognized "{}"'.format(unused_args) + 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 @@ -180,7 +257,7 @@ def main(custom_commandline=None): 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') + raise esptool.FatalError("BURN was not done") if common_args.virt is False: esp._port.close() @@ -190,7 +267,7 @@ def _main(): try: main() except esptool.FatalError as e: - print('\nA fatal error occurred: %s' % e) + print("\nA fatal error occurred: %s" % e) sys.exit(2) diff --git a/espefuse/efuse/base_fields.py b/espefuse/efuse/base_fields.py index 401ae85..63868ca 100644 --- a/espefuse/efuse/base_fields.py +++ b/espefuse/efuse/base_fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes the common eFuses structures for chips # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -29,9 +30,13 @@ class CheckArgValue(object): if efuse.efuse_type.startswith("bool"): new_value = 1 if new_value is None else int(new_value, 0) if new_value != 1: - raise argparse.ArgumentTypeError("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')): + raise argparse.ArgumentTypeError( + "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 @@ -45,18 +50,36 @@ class CheckArgValue(object): new_value = int(new_value, 0) else: if new_value is None: - raise argparse.ArgumentTypeError("New value required for efuse '{}' (given None)".format(efuse.name)) + raise argparse.ArgumentTypeError( + "New value required for efuse '{}' (given None)".format( + efuse.name + ) + ) new_value = int(new_value, 0) if new_value == 0: - raise argparse.ArgumentTypeError("New value should not be 0 for '{}' (given value= {})".format(efuse.name, new_value)) + raise argparse.ArgumentTypeError( + "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 argparse.ArgumentTypeError("New value required for efuse '{}' (given None)".format(efuse.name)) + raise argparse.ArgumentTypeError( + "New value required for efuse '{}' " + "(given None)".format(efuse.name) + ) if len(new_value) * 8 != efuse.bitarray.len: - raise argparse.ArgumentTypeError("The length of efuse '{}' ({} bits) (given len of the new value= {} bits)" - .format(efuse.name, efuse.bitarray.len, len(new_value) * 8)) + raise argparse.ArgumentTypeError( + "The length of efuse '{}' ({} bits) " + "(given len of the new value= {} bits)".format( + efuse.name, efuse.bitarray.len, len(new_value) * 8 + ) + ) else: - raise argparse.ArgumentTypeError("The '{}' type for the '{}' efuse is not supported yet.".format(efuse.efuse_type, efuse.name)) + raise argparse.ArgumentTypeError( + "The '{}' type for the '{}' efuse is not supported yet.".format( + efuse.efuse_type, efuse.name + ) + ) return new_value efuse = self.efuses[self.name] @@ -71,13 +94,13 @@ class EfuseProtectBase(object): mask = 0 if isinstance(self.read_disable_bit, list): for i in self.read_disable_bit: - mask |= (1 << i) + mask |= 1 << i else: - mask = (1 << self.read_disable_bit) + mask = 1 << self.read_disable_bit return mask def is_readable(self): - """ Return true if the efuse is readable by software """ + """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 @@ -88,7 +111,10 @@ class EfuseProtectBase(object): 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") + 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): @@ -100,16 +126,22 @@ class EfuseProtectBase(object): 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") + 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. The written value can not be read, the efuse/block looks as all 0.\n".format(self.name) + 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) + error_msg = "\t{} is write-protected. Burn is not possible.".format( + self.name + ) self.parent.print_error_msg(error_msg) @@ -150,7 +182,9 @@ class EfuseBlockBase(EfuseProtectBase): elif coding_scheme == self.parent.REGS.CODING_SCHEME_RS: return self.len * 4 else: - raise esptool.FatalError("Coding scheme (%d) not supported" % (coding_scheme)) + raise esptool.FatalError( + "Coding scheme (%d) not supported" % (coding_scheme) + ) def get_coding_scheme(self): if self.id == 0: @@ -182,6 +216,7 @@ class EfuseBlockBase(EfuseProtectBase): 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): @@ -195,8 +230,18 @@ class EfuseBlockBase(EfuseProtectBase): 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]])) + 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 @@ -206,16 +251,25 @@ class EfuseBlockBase(EfuseProtectBase): 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))) + 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)) + 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)) + 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)) @@ -223,7 +277,10 @@ class EfuseBlockBase(EfuseProtectBase): 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.") + 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() @@ -231,42 +288,62 @@ class EfuseBlockBase(EfuseProtectBase): 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) + 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:] + 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="") + 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) + 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) + 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)) + 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) + # 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 = BitString(bytes=new_data[::-1], length=len(new_data) * 8) if self.parent.debug: - print("\twritten : {} ->\n\tto write: {}".format(self.get_bitstring(), data)) + 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): @@ -278,9 +355,11 @@ class EfuseBlockBase(EfuseProtectBase): 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 + # 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 + # 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) @@ -291,7 +370,11 @@ class EfuseBlockBase(EfuseProtectBase): 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)) + 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 @@ -307,24 +390,40 @@ class EfuseBlockBase(EfuseProtectBase): 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)) + 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)) + 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: + 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 + # 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 + # 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)) + raise esptool.FatalError( + "Burn {} ({}) was not successful".format(self.name, self.alias) + ) self.wr_bitarray.set(0) @@ -333,9 +432,9 @@ class EspEfusesBase(object): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - _esp = None - blocks = [] - efuses = [] + _esp = None + blocks = [] + efuses = [] coding_scheme = None force_write_always = None batch_mode_cnt = 0 @@ -347,7 +446,7 @@ class EspEfusesBase(object): return self._esp.get_crystal_freq() def read_efuse(self, n): - """ Read the nth word of the ESP3x EFUSE region. """ + """Read the nth word of the ESP3x EFUSE region.""" return self._esp.read_efuse(n) def read_reg(self, addr): @@ -379,14 +478,19 @@ class EspEfusesBase(object): 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.") + 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): + 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.") @@ -398,7 +502,9 @@ class EspEfusesBase(object): old_fail = block.fail old_num_errors = block.num_errors block.burn() - if (block.fail and old_fail != block.fail) or (block.num_errors and block.num_errors > old_num_errors): + if (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() @@ -408,10 +514,14 @@ class EspEfusesBase(object): @staticmethod def confirm(action, do_not_confirm): - print("%s%s\nThis is an irreversible operation!" % (action, "" if action.endswith("\n") else ". ")) + 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.") - sys.stdout.flush() # required for Pythons which disable line buffering, ie mingw in mintty + # required for Pythons which disable line buffering, ie mingw in mintty + sys.stdout.flush() try: yes = raw_input() # raw_input renamed to input in Python 3 except NameError: @@ -449,7 +559,7 @@ class EfuseFieldBase(EfuseProtectBase): if self.efuse_type.startswith("bool"): field_len = 1 else: - field_len = int(re.search(r'\d+', self.efuse_type).group()) + field_len = int(re.search(r"\d+", self.efuse_type).group()) if self.efuse_type.startswith("bytes"): field_len *= 8 self.bitarray = BitString(field_len) @@ -480,11 +590,16 @@ class EfuseFieldBase(EfuseProtectBase): 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) + # 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: @@ -493,14 +608,20 @@ class EfuseFieldBase(EfuseProtectBase): 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)) + 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 {} is already burned. Do not change the efuse.".format(self.name) + 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 {} is already prepared for the burn operation.".format(self.name) + 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: @@ -508,14 +629,19 @@ class EfuseFieldBase(EfuseProtectBase): # 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 (value will be {})".format(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) + 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 @@ -526,18 +652,22 @@ class EfuseFieldBase(EfuseProtectBase): def update(self, bit_array_block): field_len = self.bitarray.len - bit_array_block.pos = bit_array_block.length - (self.word * 32 + self.pos + field_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) + 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 + """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 @@ -546,9 +676,10 @@ class EfuseFieldBase(EfuseProtectBase): 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 + """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 ... + 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], " ") @@ -556,7 +687,7 @@ class EfuseFieldBase(EfuseProtectBase): 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 """ + """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)] @@ -571,7 +702,9 @@ class EfuseFieldBase(EfuseProtectBase): 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) + 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): diff --git a/espefuse/efuse/base_operations.py b/espefuse/efuse/base_operations.py index bbff98d..0596a3b 100644 --- a/espefuse/efuse/base_operations.py +++ b/espefuse/efuse/base_operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the common operations with eFuses for chips # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -27,100 +28,222 @@ def add_common_commands(subparsers, efuses): self.efuses = kwargs.get("efuses") del kwargs["efuse_choices"] del kwargs["efuses"] - super(ActionEfuseValuePair, self).__init__(option_strings, dest, nargs=nargs, **kwargs) + 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)) + 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)) + 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:] + 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 - # The fields with 'bitcount' and 'bool' types can be without new_value arg + # 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], - efuses=efuses) + 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], + 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]) + 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], + ) - 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]) + 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], + ) - 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)).") + 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')) + 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')) + 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.") + 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) + 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.') + 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.') + 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) + 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')) + 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') + 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') + 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 summary(esp, efuses, args): - """ Print a human-readable summary of efuse contents """ + """Print a human-readable summary of efuse contents""" ROW_FORMAT = "%-50s %-50s%s = %s %s %s" - human_output = (args.format == 'summary') + 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( + 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: @@ -144,50 +267,72 @@ def summary(esp, efuses, args): if not readable: 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) + 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': + 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} + "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) + 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': + 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 """ + """Dump raw efuse data registers""" # Using --debug option allows to print dump. - # Nothing to do here. The log will be print during EspEfuses.__init__() in self.read_blocks() + # 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:] + 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() @@ -197,9 +342,15 @@ def dump(esp, efuses, args): 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:") + 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:]))) + 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] @@ -215,31 +366,57 @@ def burn_efuse(esp, efuses, args): 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] + 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] + 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))) + print( + "\n - '{}' ({}) {} -> {}".format( + efuse.name, + efuse.description, + efuse.get_bitstring(), + efuse.convert_to_bitstring(new_value), + ) + ) efuse.save(new_value) 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): + 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) + 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) + 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.") @@ -256,27 +433,55 @@ def read_protect_efuse(esp, efuses, args): 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 "revision 3" in esp.get_chip_description(): - 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!") + if ( + efuse_name == "BLOCK2" + and not efuses["ABS_DONE_0"].get() + and "revision 3" in esp.get_chip_description() + ): + 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("In case using Secure Boot V2, the BLOCK2 must be readable, please stop this operation!") + 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'] + 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) + 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) + 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] + # 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)) + 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): @@ -302,10 +507,16 @@ def write_protect_efuse(esp, efuses, args): 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] + # 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)) + 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): @@ -325,13 +536,20 @@ def write_protect_efuse(esp, efuses, args): 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]):] + 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.") + 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: @@ -339,9 +557,15 @@ def burn_block_data(esp, efuses, args): 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)) + 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))) + 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) @@ -349,12 +573,19 @@ def burn_block_data(esp, efuses, args): 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))) + 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, " "))) + 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): @@ -371,9 +602,15 @@ def burn_bit(esp, efuses, args): 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)) + 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( + "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]) @@ -394,9 +631,12 @@ def check_error(esp, efuses, args): block.save(block.get_bitstring().bytes[::-1]) if not confirmed: confirmed = True - efuses.confirm("Recovery of block coding errors", args.do_not_confirm) + efuses.confirm( + "Recovery of block coding errors", args.do_not_confirm + ) block.burn() - # Reset the recovery flag to run check_error() without it, just to check the new state of eFuse blocks. + # Reset the recovery flag to run check_error() without it, + # just to check the new state of eFuse blocks. args.recovery = False check_error(esp, efuses, args) else: diff --git a/espefuse/efuse/emulate_efuse_controller_base.py b/espefuse/efuse/emulate_efuse_controller_base.py index 68b540b..2941ec3 100644 --- a/espefuse/efuse/emulate_efuse_controller_base.py +++ b/espefuse/efuse/emulate_efuse_controller_base.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -13,36 +14,38 @@ from bitstring import BitString class EmulateEfuseControllerBase(object): - """ The class for virtual efuse operations. Using for HOST_TEST. - """ + """The class for virtual efuse operations. Using for HOST_TEST.""" CHIP_NAME = "" mem = None debug = False - Blocks = None - Fields = None - REGS = None + 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 = BitString(open(self.efuse_file, 'a+b'), length=self.REGS.EFUSE_MEM_SIZE * 8) + self.mem = BitString( + open(self.efuse_file, "a+b"), length=self.REGS.EFUSE_MEM_SIZE * 8 + ) except ValueError: # the file is empty or does not fit the length. self.mem = BitString(length=self.REGS.EFUSE_MEM_SIZE * 8) self.mem.set(0) - self.mem.tofile(open(self.efuse_file, 'a+b')) + 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 + # efuse_file is not provided + # it means we do not want to keep the result of efuse operations self.mem = BitString(self.REGS.EFUSE_MEM_SIZE * 8) self.mem.set(0) """ esptool method start >> """ def read_efuse(self, n, block=0): - """ Read the nth word of the ESP3x EFUSE region. """ + """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)) @@ -63,7 +66,7 @@ class EmulateEfuseControllerBase(object): 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. """ + """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) @@ -74,7 +77,7 @@ class EmulateEfuseControllerBase(object): def save_to_file(self): if self.efuse_file: - with open(self.efuse_file, 'wb') as f: + with open(self.efuse_file, "wb") as f: self.mem.tofile(f) def handle_coding_scheme(self, blk, data): @@ -109,7 +112,7 @@ class EmulateEfuseControllerBase(object): if field.type.startswith("bool"): field_len = 1 else: - field_len = int(re.search(r'\d+', field.type).group()) + 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) @@ -129,7 +132,9 @@ class EmulateEfuseControllerBase(object): 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) + 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 @@ -139,26 +144,34 @@ class EmulateEfuseControllerBase(object): 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.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 then we need to protect that area from changes. + # 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 = BitString(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): + if blk.write_disable_bit is not None and write_disable_bit & ( + 1 << blk.write_disable_bit + ): mask_wr_data.set(1) else: for e in self.Fields.EFUSES: field = self.Fields.get(e) 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): + 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.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 @@ -170,22 +183,30 @@ class EmulateEfuseControllerBase(object): 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): + if blk.read_disable_bit is not None and read_disable_bit & ( + 1 << blk.read_disable_bit + ): block.set(0) else: for e in self.Fields.EFUSES: field = self.Fields.get(e) - if blk.id == field.block and field.read_disable_bit is not None and read_disable_bit & (1 << field.read_disable_bit): + 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.pos = block.length - ( + field.word * 32 + field.pos + raw_data.length + ) block.overwrite(BitString(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: + with open(self.efuse_file, "wb") as f: self.mem.tofile(f) @@ -193,6 +214,7 @@ class FatalError(RuntimeError): """ Wrapper class for runtime errors that aren't caused by internal bugs """ + def __init__(self, message): RuntimeError.__init__(self, message) diff --git a/espefuse/efuse/esp32/emulate_efuse_controller.py b/espefuse/efuse/esp32/emulate_efuse_controller.py index a31053e..d412157 100644 --- a/espefuse/efuse/esp32/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operations. Using for HOST_TEST. - """ + """The class for virtual efuse operations. Using for HOST_TEST.""" + CHIP_NAME = "ESP32" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -42,7 +43,10 @@ class EmulateEfuseController(EmulateEfuseControllerBase): 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") + 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) @@ -61,13 +65,21 @@ class EmulateEfuseController(EmulateEfuseControllerBase): 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 + 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.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") + 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): @@ -83,7 +95,9 @@ class EmulateEfuseController(EmulateEfuseControllerBase): else: return 32 * blk.len * 3 // 4 else: - raise FatalError("The {} coding scheme is not supported".format(coding_scheme)) + 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 @@ -95,16 +109,23 @@ class EmulateEfuseController(EmulateEfuseControllerBase): for _ in range(0, 4): xor_res = 0 mul_res = 0 - chunk_data = data.readlist('8*uint:8') + 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') + 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]) + 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] + del data[i * 6 * 8 : (i * 6 * 8) + 16] return data diff --git a/espefuse/efuse/esp32/fields.py b/espefuse/efuse/esp32/fields.py index df53020..be32cb2 100644 --- a/espefuse/efuse/esp32/fields.py +++ b/espefuse/efuse/esp32/fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -68,9 +69,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -82,29 +83,77 @@ class EspEfuses(base_fields.EspEfusesBase): 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_256] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.CUSTOM_MAC] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.ADC_CALIBRATION] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS_256 + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.CUSTOM_MAC + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.ADC_CALIBRATION + ] else: if self.coding_scheme == self.REGS.CODING_SCHEME_NONE: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_256] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS_256 + ] elif self.coding_scheme == self.REGS.CODING_SCHEME_34: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS_192] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS_192 + ] else: - raise esptool.FatalError("The coding scheme (%d) - is not supported" % self.coding_scheme) + raise esptool.FatalError( + "The coding scheme (%d) - is not supported" % self.coding_scheme + ) if self["MAC_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.CUSTOM_MAC] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.CUSTOM_MAC + ] if self["BLK3_PART_RESERVE"].get(): - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.ADC_CALIBRATION] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.ADC_CALIBRATION + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -112,12 +161,22 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.CUSTOM_MAC: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.CUSTOM_MAC] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.CUSTOM_MAC + ] new_fields = True for efuse in self.Fields.ADC_CALIBRATION: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.ADC_CALIBRATION] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.ADC_CALIBRATION + ] new_fields = True if new_fields: for e in self.efuses: @@ -126,14 +185,21 @@ class EspEfuses(base_fields.EspEfusesBase): 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 + 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))) + 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 + """Write the values in the efuse write registers to the efuse hardware, then refresh the efuse read registers. """ @@ -141,9 +207,15 @@ class EspEfuses(base_fields.EspEfusesBase): 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.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) @@ -156,7 +228,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_read(self): self.wait_efuse_idle() @@ -165,23 +239,30 @@ class EspEfuses(base_fields.EspEfusesBase): self.wait_efuse_idle() def get_coding_scheme_warnings(self, silent=False): - """ Check if the coding scheme has detected any errors. + """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 + 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)) + 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)." + 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: @@ -213,15 +294,24 @@ 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -232,7 +322,10 @@ class EfuseMacField(EfuseField): 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) + valid_msg = "(CRC 0x%02x invalid - calculated 0x%02x)" % ( + stored_crc, + computed_crc, + ) return "%s %s" % (util.hexify(raw_mac, ":"), valid_msg) @staticmethod @@ -249,7 +342,7 @@ class EfuseMacField(EfuseField): lsb = result & 1 result >>= 1 if lsb != 0: - result ^= 0x8c + result ^= 0x8C return result def get(self, from_read=True): @@ -263,7 +356,11 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), new_value)) + print( + " - '{}' ({}) {} -> {}".format( + e.name, e.description, e.get_bitstring(), new_value + ) + ) if self.name == "CUSTOM_MAC": # Writing the BLK3: @@ -278,7 +375,11 @@ class EfuseMacField(EfuseField): 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())) + 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) @@ -289,7 +390,8 @@ class EfuseMacField(EfuseField): print_field(crc_field, hex(crc_val)) crc_field.save(crc_val) else: - # Writing the BLK0 default MAC is not sensible, as it's written in the factory. + # 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") @@ -307,9 +409,14 @@ class EfuseSpiPinField(EfuseField): 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.") + 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) + 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: @@ -325,10 +432,10 @@ class EfuseVRefField(EfuseField): def get(self, from_read=True): val = self.get_raw(from_read) # sign-magnitude format - if (val & self.VREF_SIGN_BIT): + if val & self.VREF_SIGN_BIT: val = -(val & self.VREF_MAG_BITS) else: - val = (val & self.VREF_MAG_BITS) + val = val & self.VREF_MAG_BITS val *= self.VREF_STEP_SIZE return self.VREF_OFFSET + val diff --git a/espefuse/efuse/esp32/mem_definition.py b/espefuse/efuse/esp32/mem_definition.py index 6f6969d..dfad97b 100644 --- a/espefuse/efuse/esp32/mem_definition.py +++ b/espefuse/efuse/esp32/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -12,42 +13,44 @@ from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegiste class EfuseDefineRegisters(EfuseRegistersBase): - EFUSE_MEM_SIZE = (0x011C + 4) + 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 + 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 + 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_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_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), # this is here for completeness only as esptool never sets an 80MHz APB clock + 80: (80, 128, 100), } +# fmt: off class EfuseDefineBlocks(EfuseBlocksBase): __base_regs = EfuseDefineRegisters.DR_REG_EFUSE_BASE @@ -148,3 +151,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_TP_LOW', "calibration", 3, 3, 16, "uint:7", 9, 2, "adc_tp", "ADC2 150mV reading", None), ('ADC2_TP_HIGH', "calibration", 3, 3, 23, "uint:9", 9, 2, "adc_tp", "ADC2 850mV reading", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32/operations.py b/espefuse/efuse/esp32/operations.py index dd65592..5572ebd 100644 --- a/espefuse/efuse/esp32/operations.py +++ b/espefuse/efuse/esp32/operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the operations with eFuses for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -17,42 +18,103 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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) - 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')) + 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')) + 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') + 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) - 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -70,7 +132,11 @@ def burn_custom_mac(esp, efuses, args): 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())) + print( + "Custom MAC Address version {}: {}".format( + version, efuses["CUSTOM_MAC"].get() + ) + ) else: print("Custom MAC Address is not set in the device.") @@ -81,34 +147,37 @@ def set_flash_voltage(esp, efuses, args): 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 == "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 == "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. -The following efuse is burned: XPD_SDIO_FORCE. -It is possible to later re-enable the internal regulator (%s) by burning an additional efuse -""" % ("to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V") - elif args.voltage == '1.8V': - msg = """ -Set internal flash voltage regulator (VDD_SDIO) to 1.8V. -The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG. -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. -The following efuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG, XPD_SDIO_TIEH. -""" + 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_force.save(1) # Disable GPIO12 + if args.voltage != "OFF": sdio_reg.save(1) # Enable internal regulator - if args.voltage == '3.3V': + if args.voltage == "3.3V": sdio_tieh.save(1) print("VDD_SDIO setting complete.") if not efuses.burn_all(check_batch_mode=True): @@ -135,14 +204,21 @@ def adc_info(esp, efuses, args): 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]):] + 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))) + 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): @@ -162,8 +238,11 @@ def burn_key(esp, efuses, args): 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)) + 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) @@ -182,10 +261,13 @@ def burn_key(esp, efuses, args): 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)" + 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') + 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") @@ -197,14 +279,20 @@ def burn_key_digest(esp, efuses, args): chip_revision = esp.get_chip_description() if "revision 3" not in chip_revision: - raise esptool.FatalError("Incorrect chip revision for Secure boot v2. Detected: %s. Expected: (revision 3)" % chip_revision) + raise esptool.FatalError( + "Incorrect chip revision for Secure boot v2. " + "Detected: %s. Expected: (revision 3)" % chip_revision + ) 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)) + 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 -> [%s]" % (efuse.name, util.hexify(digest, " "))) efuse.save(digest) @@ -219,19 +307,19 @@ def burn_key_digest(esp, efuses, args): def espefuse(esp, efuses, args, command): parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest='operation') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -249,8 +337,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32c2/emulate_efuse_controller.py b/espefuse/efuse/esp32c2/emulate_efuse_controller.py index 76fb223..07b8501 100644 --- a/espefuse/efuse/esp32c2/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32c2/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-C2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -16,14 +17,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-C2" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -79,18 +80,18 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data def check_rd_protection_area(self): @@ -101,19 +102,24 @@ class EmulateEfuseController(EmulateEfuseControllerBase): mask = 0 if isinstance(blk.read_disable_bit, list): for i in blk.read_disable_bit: - mask |= (1 << i) + mask |= 1 << i else: - mask = (1 << blk.read_disable_bit) + 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 ( + 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)]) + 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: @@ -121,9 +127,15 @@ class EmulateEfuseController(EmulateEfuseControllerBase): else: for e in self.Fields.EFUSES: field = self.Fields.get(e) - if blk.id == field.block and field.read_disable_bit is not None and read_disable_bit & get_read_disable_mask(field): + 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.pos = block.length - ( + field.word * 32 + field.pos + raw_data.length + ) block.overwrite(BitString(raw_data.length)) self.overwrite_mem_from_block(blk, block) diff --git a/espefuse/efuse/esp32c2/fields.py b/espefuse/efuse/esp32c2/fields.py index baf9d2f..540bacc 100644 --- a/espefuse/efuse/esp32c2/fields.py +++ b/espefuse/efuse/esp32c2/fields.py @@ -38,7 +38,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -59,9 +59,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -73,27 +73,53 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -101,8 +127,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -116,10 +146,14 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + print( + "{:27} 0x{:08x}".format( + "EFUSE_RD_RS_ERR_REG", self.read_reg(self.REGS.EFUSE_RD_RS_ERR_REG) + ) + ) def get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -133,7 +167,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -142,7 +178,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -157,35 +195,47 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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) + raise esptool.FatalError( + "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq + ) - self.update_reg(self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR_REG + offs * 4) + for offs in range(1) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -195,7 +245,10 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 @@ -209,17 +262,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -245,15 +302,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -275,7 +341,11 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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) @@ -287,10 +357,22 @@ class EfuseMacField(EfuseField): class EfuseKeyPurposeField(EfuseField): KEY_PURPOSES = [ - ("USER", 0, None), # User purposes (software-only use) - ("XTS_AES_128_KEY", 1, None), # (whole 256bits) XTS_AES_128_KEY (flash/PSRAM encryption) - ("XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS", 2, None), # (lo 128bits) XTS_AES_128_KEY (flash/PSRAM encryption) - ("SECURE_BOOT_DIGEST", 3, "DIGEST"), # (hi 128bits)SECURE_BOOT_DIGEST (Secure Boot key digest) + ("USER", 0, None), # User purposes (software-only use) + ( + "XTS_AES_128_KEY", + 1, + None, + ), # (whole 256bits) XTS_AES_128_KEY (flash/PSRAM encryption) + ( + "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS", + 2, + None, + ), # (lo 128bits) XTS_AES_128_KEY (flash/PSRAM encryption) + ( + "SECURE_BOOT_DIGEST", + 3, + "DIGEST", + ), # (hi 128bits)SECURE_BOOT_DIGEST (Secure Boot key digest) ] KEY_PURPOSES_NAME = [name[0] for name in KEY_PURPOSES] diff --git a/espefuse/efuse/esp32c2/mem_definition.py b/espefuse/efuse/esp32c2/mem_definition.py index f3cb531..e105de3 100644 --- a/espefuse/efuse/esp32c2/mem_definition.py +++ b/espefuse/efuse/esp32c2/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32-C2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_MEM_SIZE = (0x01FC + 4) @@ -152,3 +154,4 @@ class EfuseDefineFields(EfuseFieldsBase): BLOCK2_CALIBRATION_EFUSES = [ # Name Category Block Word Pos Type:len WR_DIS RD_DIS Class Description Dictionary ] +# fmt: on diff --git a/espefuse/efuse/esp32c2/operations.py b/espefuse/efuse/esp32c2/operations.py index 00a96b6..0d348b1 100644 --- a/espefuse/efuse/esp32c2/operations.py +++ b/espefuse/efuse/esp32c2/operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the operations with eFuses for ESP32-C2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -17,48 +18,119 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - burn_key_digest.add_argument('keyfile', help='Key file to digest (PEM format)', type=argparse.FileType('rb')) + 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -80,6 +152,7 @@ def set_flash_voltage(esp, efuses, args): def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print(" RF_REF_I_BIAS_CONFIG: {}".format(efuses["RF_REF_I_BIAS_CONFIG"].get())) @@ -95,77 +168,107 @@ def adc_info(esp, efuses, args): else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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]):] + 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]):] + 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]):] + 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))) + 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' + 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 + 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)) + 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 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') + 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' + 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'] + efuse = efuses["BLOCK_KEY0"] num_bytes = efuse.bit_len // 8 print(" - %s" % (efuse.name), end=" ") revers_msg = None - if keypurpose.startswith('XTS_AES_'): + if keypurpose.startswith("XTS_AES_"): revers_msg = "\tReversing byte order for AES-XTS hardware peripheral" data = data[::-1] print("-> [%s]" % (util.hexify(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)) + 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_'): + 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. + # 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(): + 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) + efuses["XTS_KEY_LENGTH_256"].save(1) if read_protect: print("\tDisabling read to key block") @@ -188,32 +291,35 @@ def burn_key(esp, efuses, args, digest=None): def burn_key_digest(esp, efuses, args): datafile = args.keyfile - args.keypurpose = ['SECURE_BOOT_DIGEST'] - args.block = ['BLOCK_KEY0'] + 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 + 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -231,8 +337,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32c3/emulate_efuse_controller.py b/espefuse/efuse/esp32c3/emulate_efuse_controller.py index 3e843d3..94ed608 100644 --- a/espefuse/efuse/esp32c3/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32c3/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-C3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-C3" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -77,16 +78,16 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data diff --git a/espefuse/efuse/esp32c3/fields.py b/espefuse/efuse/esp32c3/fields.py index 28d9d0f..84b59a6 100644 --- a/espefuse/efuse/esp32c3/fields.py +++ b/espefuse/efuse/esp32c3/fields.py @@ -38,7 +38,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -59,9 +59,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -73,27 +73,53 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -101,8 +127,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -116,11 +146,19 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + 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 get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -134,7 +172,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -143,7 +183,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -158,35 +200,47 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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) + raise esptool.FatalError( + "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq + ) - self.update_reg(self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) + for offs in range(5) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -196,7 +250,10 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 @@ -210,17 +267,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -246,15 +307,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -276,17 +346,23 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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 sensible, as it's written in the factory. + # 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) @@ -302,7 +378,7 @@ class EfuseKeyPurposeField(EfuseField): ("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"] diff --git a/espefuse/efuse/esp32c3/mem_definition.py b/espefuse/efuse/esp32c3/mem_definition.py index 4e0cb3a..40fd581 100644 --- a/espefuse/efuse/esp32c3/mem_definition.py +++ b/espefuse/efuse/esp32c3/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32-C3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_MEM_SIZE = (0x01FC + 4) @@ -220,3 +222,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_MODE2_D1', "calibration", 2, 7, 20, "uint:6", 21, None, "adc_tp", "ADC2 calibration 15", None), ('ADC2_MODE3_D1', "calibration", 2, 7, 26, "uint:6", 21, None, "adc_tp", "ADC2 calibration 16", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32c3/operations.py b/espefuse/efuse/esp32c3/operations.py index 116ae47..5e869d4 100644 --- a/espefuse/efuse/esp32c3/operations.py +++ b/espefuse/efuse/esp32c3/operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the operations with eFuses for ESP32-C3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -17,58 +18,162 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - 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) + 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) + 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -89,6 +194,7 @@ def set_flash_voltage(esp, efuses, args): def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) @@ -121,24 +227,38 @@ def adc_info(esp, efuses, args): print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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]):] + 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]):] + 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]):] + 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))) + 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): + 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: @@ -164,8 +284,10 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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 @@ -179,12 +301,22 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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)) + 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(): @@ -215,8 +347,12 @@ def burn_key(esp, efuses, args, digest=None): 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]):] + 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: @@ -227,27 +363,29 @@ def burn_key_digest(esp, efuses, args): 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -265,8 +403,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32h2beta1/emulate_efuse_controller.py b/espefuse/efuse/esp32h2beta1/emulate_efuse_controller.py index ca641ad..54268d2 100644 --- a/espefuse/efuse/esp32h2beta1/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32h2beta1/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-H2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-H2(beta1)" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -77,16 +78,16 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data diff --git a/espefuse/efuse/esp32h2beta1/fields.py b/espefuse/efuse/esp32h2beta1/fields.py index 291beb7..7daad78 100644 --- a/espefuse/efuse/esp32h2beta1/fields.py +++ b/espefuse/efuse/esp32h2beta1/fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses for ESP32-H2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -36,7 +37,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -57,9 +58,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -71,27 +72,53 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -99,8 +126,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -114,11 +145,19 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + 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 get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -132,7 +171,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -141,7 +182,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.REGS.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -156,35 +199,47 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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) + raise esptool.FatalError( + "The eFuse supports only xtal=32M (xtal was %d)" % apb_freq + ) - self.update_reg(self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) + for offs in range(5) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -194,7 +249,10 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 @@ -208,17 +266,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -244,15 +306,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -267,7 +338,9 @@ class EfuseMacField(EfuseField): def get(self, from_read=True): if self.name == "CUSTOM_MAC": - mac = self.get_raw(from_read)[::-1] + self.parent["MAC_EXT"].get_raw(from_read) + mac = self.get_raw(from_read)[::-1] + self.parent["MAC_EXT"].get_raw( + from_read + ) elif self.name == "MAC": mac = self.get_raw(from_read) + self.parent["MAC_EXT"].get_raw(from_read) else: @@ -276,17 +349,23 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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 sensible, as it's written in the factory. + # 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) @@ -302,6 +381,7 @@ class EfuseKeyPurposeField(EfuseField): ("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"] diff --git a/espefuse/efuse/esp32h2beta1/mem_definition.py b/espefuse/efuse/esp32h2beta1/mem_definition.py index f4ea93e..e55fd61 100644 --- a/espefuse/efuse/esp32h2beta1/mem_definition.py +++ b/espefuse/efuse/esp32h2beta1/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32-H2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_MEM_SIZE = (0x01FC + 4) @@ -225,3 +227,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_MODE2_D1', "calibration", 2, 7, 20, "uint:6", 21, None, "adc_tp", "ADC2 calibration 15", None), ('ADC2_MODE3_D1', "calibration", 2, 7, 26, "uint:6", 21, None, "adc_tp", "ADC2 calibration 16", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32h2beta1/operations.py b/espefuse/efuse/esp32h2beta1/operations.py index 30bcd99..721092b 100644 --- a/espefuse/efuse/esp32h2beta1/operations.py +++ b/espefuse/efuse/esp32h2beta1/operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the operations with eFuses for ESP32-H2 chip # # SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD @@ -17,59 +18,162 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - 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) + 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) + 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( + "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). Final CUSTOM_MAC = CUSTOM_MAC[48] + MAC_EXT[16]', - type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC")) + 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). " + "Final CUSTOM_MAC = CUSTOM_MAC[48] + MAC_EXT[16]", + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -90,6 +194,7 @@ def set_flash_voltage(esp, efuses, args): def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) @@ -122,24 +227,38 @@ def adc_info(esp, efuses, args): print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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]):] + 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]):] + 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]):] + 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))) + 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): + 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: @@ -165,8 +284,10 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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 @@ -180,12 +301,21 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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)) + 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(): @@ -216,8 +346,12 @@ def burn_key(esp, efuses, args, digest=None): 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]):] + 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: @@ -228,27 +362,29 @@ def burn_key_digest(esp, efuses, args): 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -266,8 +402,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32s2/emulate_efuse_controller.py b/espefuse/efuse/esp32s2/emulate_efuse_controller.py index 97bf705..c731a9c 100644 --- a/espefuse/efuse/esp32s2/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32s2/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-S2 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-S2" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -77,16 +78,16 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data diff --git a/espefuse/efuse/esp32s2/fields.py b/espefuse/efuse/esp32s2/fields.py index c5fadca..25e2637 100644 --- a/espefuse/efuse/esp32s2/fields.py +++ b/espefuse/efuse/esp32s2/fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses for ESP32S2 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -36,7 +37,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -57,9 +58,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -71,27 +72,53 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -99,8 +126,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -114,11 +145,19 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + 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 get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -132,7 +171,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -141,7 +182,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -156,46 +199,93 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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_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_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) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) + for offs in range(5) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -205,15 +295,20 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 (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 VDD3P3_RTC_IO via resistor Rspi. Typically this voltage is 3.3 V)." + output = "Flash voltage (VDD_SPI) determined by GPIO45 on reset " + "(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 " + "VDD3P3_RTC_IO via resistor Rspi. 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: @@ -227,17 +322,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -263,15 +362,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -293,17 +401,23 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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 sensible, as it's written in the factory. + # 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) @@ -320,6 +434,7 @@ class EfuseKeyPurposeField(EfuseField): ("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"] diff --git a/espefuse/efuse/esp32s2/mem_definition.py b/espefuse/efuse/esp32s2/mem_definition.py index 002223c..d6a8fa9 100644 --- a/espefuse/efuse/esp32s2/mem_definition.py +++ b/espefuse/efuse/esp32s2/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_MEM_SIZE = (0x01FC + 4) @@ -299,3 +301,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_MODE2_D1', "calibration", 2, 7, 20, "uint:6", 21, None, "adc_tp", "ADC2 calibration 15", None), ('ADC2_MODE3_D1', "calibration", 2, 7, 26, "uint:6", 21, None, "adc_tp", "ADC2 calibration 16", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32s2/operations.py b/espefuse/efuse/esp32s2/operations.py index b07e8e9..a486ba9 100644 --- a/espefuse/efuse/esp32s2/operations.py +++ b/espefuse/efuse/esp32s2/operations.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file includes the operations with eFuses for ESP32S2 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -18,58 +19,162 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - 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) + 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) + 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -90,35 +195,38 @@ def set_flash_voltage(esp, efuses, args): 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 == "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 == "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. -The following efuse is burned: VDD_SPI_FORCE. -It is possible to later re-enable the internal regulator (%s) by burning an additional efuse -""" % ("to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V") - elif args.voltage == '1.8V': - msg = """ -Set internal flash voltage regulator (VDD_SPI) to 1.8V. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD. -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. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. -""" + 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_force.save(1) # Disable GPIO45 + if args.voltage != "OFF": sdio_reg.save(1) # Enable internal regulator - if args.voltage == '3.3V': + if args.voltage == "3.3V": sdio_tieh.save(1) print("VDD_SPI setting complete.") @@ -129,6 +237,7 @@ The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) @@ -161,13 +270,14 @@ def adc_info(esp, efuses, args): print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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(): + if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable(): return False if not block.get_bitstring().all(False): @@ -195,7 +305,7 @@ def get_next_key_block(efuses, current_key_block, block_name_list): def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): - i = keypurpose_list.index('XTS_AES_256_KEY') + i = keypurpose_list.index("XTS_AES_256_KEY") block_name = block_name_list[i] block_num = efuses.get_index_block_by_name(block_name) @@ -203,17 +313,19 @@ def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 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)) + 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') + raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks") - keypurpose_list.append('XTS_AES_256_KEY_1') + 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') + keypurpose_list.append("XTS_AES_256_KEY_2") datafile_list.append(io.BytesIO(data[32:])) block_name_list.append(key_block_2.name) @@ -224,25 +336,38 @@ def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 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]):] + 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]):] + 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]):] + 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: + 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))) + 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): + 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: @@ -268,8 +393,10 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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 @@ -277,18 +404,29 @@ def burn_key(esp, efuses, args, digest=None): 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. + # 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)) + 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)) + 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(): @@ -319,8 +457,12 @@ def burn_key(esp, efuses, args, digest=None): 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]):] + 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: @@ -331,27 +473,29 @@ def burn_key_digest(esp, efuses, args): 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -369,8 +513,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32s3/emulate_efuse_controller.py b/espefuse/efuse/esp32s3/emulate_efuse_controller.py index 0172249..c33149e 100644 --- a/espefuse/efuse/esp32s3/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32s3/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-S3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-S3" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -77,16 +78,16 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data diff --git a/espefuse/efuse/esp32s3/fields.py b/espefuse/efuse/esp32s3/fields.py index e0bb5d9..d580d80 100644 --- a/espefuse/efuse/esp32s3/fields.py +++ b/espefuse/efuse/esp32s3/fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses for ESP32-S3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -36,7 +37,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -57,9 +58,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -71,27 +72,53 @@ class EspEfuses(base_fields.EspEfusesBase): self.debug = debug self.do_not_confirm = do_not_confirm if esp.CHIP_NAME != "ESP32-S3": - raise esptool.FatalError("Expected the 'esp' param for ESP32-S3 chip but got for '%s'." % (esp.CHIP_NAME)) + raise esptool.FatalError( + "Expected the 'esp' param for ESP32-S3 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -99,8 +126,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -114,11 +145,19 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + 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 get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -132,7 +171,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -141,7 +182,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -156,35 +199,47 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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) + raise esptool.FatalError( + "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq + ) - self.update_reg(self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) + for offs in range(5) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -194,15 +249,20 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 (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 VDD3P3_RTC_IO via resistor Rspi. Typically this voltage is 3.3 V)." + output = "Flash voltage (VDD_SPI) determined by GPIO45 on reset " + "(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 " + "VDD3P3_RTC_IO via resistor Rspi. 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: @@ -216,17 +276,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -252,15 +316,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -282,17 +355,23 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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 sensible, as it's written in the factory. + # Writing the BLOCK1 (MAC_SPI_8M_0) default MAC is not sensible, + # 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) @@ -309,6 +388,7 @@ class EfuseKeyPurposeField(EfuseField): ("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"] diff --git a/espefuse/efuse/esp32s3/mem_definition.py b/espefuse/efuse/esp32s3/mem_definition.py index 66b3c98..f9155a8 100644 --- a/espefuse/efuse/esp32s3/mem_definition.py +++ b/espefuse/efuse/esp32s3/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32-S3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_ADDR_MASK = 0x00000FFF @@ -245,3 +247,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_MODE2_D1', "calibration", 2, 7, 20, "uint:6", 21, None, "adc_tp", "??? ADC2 calibration 15", None), ('ADC2_MODE3_D1', "calibration", 2, 7, 26, "uint:6", 21, None, "adc_tp", "??? ADC2 calibration 16", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32s3/operations.py b/espefuse/efuse/esp32s3/operations.py index ac3ee73..8822823 100644 --- a/espefuse/efuse/esp32s3/operations.py +++ b/espefuse/efuse/esp32s3/operations.py @@ -18,58 +18,162 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - 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) + 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) + 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -90,35 +194,38 @@ def set_flash_voltage(esp, efuses, args): 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 == "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 == "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. -The following efuse is burned: VDD_SPI_FORCE. -It is possible to later re-enable the internal regulator (%s) by burning an additional efuse -""" % ("to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V") - elif args.voltage == '1.8V': - msg = """ -Set internal flash voltage regulator (VDD_SPI) to 1.8V. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD. -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. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. -""" + 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_force.save(1) # Disable GPIO45 + if args.voltage != "OFF": sdio_reg.save(1) # Enable internal regulator - if args.voltage == '3.3V': + if args.voltage == "3.3V": sdio_tieh.save(1) print("VDD_SPI setting complete.") if not efuses.burn_all(check_batch_mode=True): @@ -128,6 +235,7 @@ The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) @@ -160,13 +268,14 @@ def adc_info(esp, efuses, args): print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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(): + if key_purpose_block.get() != "USER" or not key_purpose_block.is_writeable(): return False if not block.get_bitstring().all(False): @@ -194,7 +303,7 @@ def get_next_key_block(efuses, current_key_block, block_name_list): def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): - i = keypurpose_list.index('XTS_AES_256_KEY') + i = keypurpose_list.index("XTS_AES_256_KEY") block_name = block_name_list[i] block_num = efuses.get_index_block_by_name(block_name) @@ -202,17 +311,19 @@ def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 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)) + 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') + raise esptool.FatalError("XTS_AES_256_KEY requires two free keyblocks") - keypurpose_list.append('XTS_AES_256_KEY_1') + 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') + keypurpose_list.append("XTS_AES_256_KEY_2") datafile_list.append(io.BytesIO(data[32:])) block_name_list.append(key_block_2.name) @@ -223,25 +334,38 @@ def split_512_bit_key(efuses, block_name_list, datafile_list, keypurpose_list): 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]):] + 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]):] + 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]):] + 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: + 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))) + 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): + 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: @@ -267,8 +391,10 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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 @@ -276,18 +402,29 @@ def burn_key(esp, efuses, args, digest=None): 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. + # 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)) + 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)) + 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(): @@ -318,8 +455,12 @@ def burn_key(esp, efuses, args, digest=None): 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]):] + 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: @@ -330,27 +471,29 @@ def burn_key_digest(esp, efuses, args): 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -368,8 +511,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/esp32s3beta2/emulate_efuse_controller.py b/espefuse/efuse/esp32s3beta2/emulate_efuse_controller.py index 5874caa..084c932 100644 --- a/espefuse/efuse/esp32s3beta2/emulate_efuse_controller.py +++ b/espefuse/efuse/esp32s3beta2/emulate_efuse_controller.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses controller for ESP32-S3(beta2) chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -14,14 +15,14 @@ from ..emulate_efuse_controller_base import EmulateEfuseControllerBase, FatalErr class EmulateEfuseController(EmulateEfuseControllerBase): - """ The class for virtual efuse operation. Using for HOST_TEST. - """ + """The class for virtual efuse operation. Using for HOST_TEST.""" + CHIP_NAME = "ESP32-S3(beta2)" mem = None debug = False - Blocks = EfuseDefineBlocks - Fields = EfuseDefineFields - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks + Fields = EfuseDefineFields + REGS = EfuseDefineRegisters def __init__(self, efuse_file=None, debug=False): super(EmulateEfuseController, self).__init__(efuse_file, debug) @@ -77,16 +78,16 @@ class EmulateEfuseController(EmulateEfuseControllerBase): # 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] + 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]: + if calc_encoded_data != data.readlist("44*uint:8")[::-1]: raise FatalError("Error in coding scheme data") - data = data[coded_bytes * 8:] + data = data[coded_bytes * 8 :] if blk.len < 8: - data = data[(8 - blk.len) * 32:] + data = data[(8 - blk.len) * 32 :] return data diff --git a/espefuse/efuse/esp32s3beta2/fields.py b/espefuse/efuse/esp32s3beta2/fields.py index 467da5f..c3ae1e7 100644 --- a/espefuse/efuse/esp32s3beta2/fields.py +++ b/espefuse/efuse/esp32s3beta2/fields.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses for ESP32-S3 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -36,7 +37,7 @@ class EfuseBlock(base_fields.EfuseBlockBase): 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) + data = data + (b"\x00" * add_empty_bytes) if self.get_coding_scheme() == self.parent.REGS.CODING_SCHEME_RS: # takes 32 bytes # apply RS encoding @@ -57,9 +58,9 @@ class EspEfuses(base_fields.EspEfusesBase): Wrapper object to manage the efuse fields in a connected ESP bootloader """ - Blocks = EfuseDefineBlocks() - Fields = EfuseDefineFields() - REGS = EfuseDefineRegisters + Blocks = EfuseDefineBlocks() + Fields = EfuseDefineFields() + REGS = EfuseDefineRegisters BURN_BLOCK_DATA_NAMES = Blocks.get_burn_block_data_names() BLOCKS_FOR_KEYS = Blocks.get_blocks_for_keys() @@ -71,27 +72,53 @@ class EspEfuses(base_fields.EspEfusesBase): self.debug = debug self.do_not_confirm = do_not_confirm if esp.CHIP_NAME != "ESP32-S3(beta2)": - raise esptool.FatalError("Expected the 'esp' param for ESP32-S3(beta2) chip but got for '%s'." % (esp.CHIP_NAME)) + raise esptool.FatalError( + "Expected the 'esp' param for ESP32-S3(beta2) 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) + 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] + 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.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.EFUSES] - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) for efuse in self.Fields.KEYBLOCKS] + self.efuses = [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.EFUSES + ] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.KEYBLOCKS + ] if skip_connect: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] else: if self["BLOCK2_VERSION"].get() == 1: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] def __getitem__(self, efuse_name): - """ Return the efuse field with the given name """ + """Return the efuse field with the given name""" for e in self.efuses: if efuse_name == e.name: return e @@ -99,8 +126,12 @@ class EspEfuses(base_fields.EspEfusesBase): for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES: e = self.Fields.get(efuse) if e.name == efuse_name: - self.efuses += [EfuseField.from_tuple(self, self.Fields.get(efuse), self.Fields.get(efuse).class_type) - for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES] + self.efuses += [ + EfuseField.from_tuple( + self, self.Fields.get(efuse), self.Fields.get(efuse).class_type + ) + for efuse in self.Fields.BLOCK2_CALIBRATION_EFUSES + ] new_fields = True if new_fields: for e in self.efuses: @@ -114,11 +145,19 @@ class EspEfuses(base_fields.EspEfusesBase): 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))) + 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 get_block_errors(self, block_num): - """ Returns (error count, failure boolean flag) """ + """Returns (error count, failure boolean flag)""" return self.blocks[block_num].num_errors, self.blocks[block_num].fail def efuse_controller_setup(self): @@ -132,7 +171,9 @@ class EspEfuses(base_fields.EspEfusesBase): 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): + 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): @@ -141,7 +182,9 @@ class EspEfuses(base_fields.EspEfusesBase): # if self.read_reg(self.EFUSE_CMD_REG) == 0: if self.read_reg(self.REGS.EFUSE_STATUS_REG) & 0x7 == 1: return - raise esptool.FatalError("Timed out waiting for Efuse controller command to complete") + raise esptool.FatalError( + "Timed out waiting for Efuse controller command to complete" + ) def efuse_program(self, block): self.wait_efuse_idle() @@ -156,35 +199,47 @@ class EspEfuses(base_fields.EspEfusesBase): 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 - self.write_reg(self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000) + self.write_reg( + self.REGS.EFUSE_CMD_REG, self.REGS.EFUSE_READ_CMD, delay_after_us=1000 + ) self.wait_efuse_idle() def set_efuse_timing(self): - """ Set timing registers for burning efuses """ + """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) + raise esptool.FatalError( + "The eFuse supports only xtal=40M (xtal was %d)" % apb_freq + ) - self.update_reg(self.REGS.EFUSE_WR_TIM_CONF2_REG, self.REGS.EFUSE_PWR_OFF_NUM_M, 0x190) + 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. """ + """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)] + words = [ + self.read_reg(self.REGS.EFUSE_RD_REPEAT_ERR0_REG + offs * 4) + for offs in range(5) + ] data = BitArray() for word in reversed(words): data.append("uint:32=%d" % word) - # pos=32 because EFUSE_WR_DIS goes first it is 32bit long and not under error control + # pos=32 because EFUSE_WR_DIS goes first it is 32bit long + # and not under error control block.err_bitarray.overwrite(data, pos=32) 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] + 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: @@ -194,15 +249,20 @@ class EspEfuses(base_fields.EspEfusesBase): 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)) + 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 (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 VDD3P3_RTC_IO via resistor Rspi. Typically this voltage is 3.3 V)." + output = "Flash voltage (VDD_SPI) determined by GPIO45 on reset " + "(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 " + "VDD3P3_RTC_IO via resistor Rspi. 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: @@ -216,17 +276,21 @@ class EfuseField(base_fields.EfuseFieldBase): @staticmethod def from_tuple(parent, efuse_tuple, type_class): return { - "mac": EfuseMacField, - "keypurpose": EfuseKeyPurposeField, - "t_sensor": EfuseTempSensor, - "adc_tp": EfuseAdcPointCalibration, + "mac": EfuseMacField, + "keypurpose": EfuseKeyPurposeField, + "t_sensor": EfuseTempSensor, + "adc_tp": EfuseAdcPointCalibration, }.get(type_class, EfuseField)(parent, efuse_tuple) def get_info(self): output = "%s (BLOCK%d)" % (self.name, self.block) errs, fail = self.parent.get_block_errors(self.block) if errs != 0 or fail: - output += "[FAIL:%d]" % (fail) if self.block == 0 else "[ERRS:%d FAIL:%d]" % (errs, fail) + output += ( + "[FAIL:%d]" % (fail) + if self.block == 0 + else "[ERRS:%d FAIL:%d]" % (errs, fail) + ) if self.efuse_class == "keyblock": name = self.parent.blocks[self.block].key_purpose_name if name is not None: @@ -252,15 +316,24 @@ class EfuseAdcPointCalibration(EfuseField): 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!") + 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 (:)!") + 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)!") + 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 + # 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 @@ -282,17 +355,23 @@ class EfuseMacField(EfuseField): def save(self, new_value): def print_field(e, new_value): - print(" - '{}' ({}) {} -> {}".format(e.name, e.description, e.get_bitstring(), 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 sensible, as it's written in the factory. + # 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) @@ -308,6 +387,7 @@ class EfuseKeyPurposeField(EfuseField): ("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"] diff --git a/espefuse/efuse/esp32s3beta2/mem_definition.py b/espefuse/efuse/esp32s3beta2/mem_definition.py index 80f9b4e..abe8fd4 100644 --- a/espefuse/efuse/esp32s3beta2/mem_definition.py +++ b/espefuse/efuse/esp32s3beta2/mem_definition.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32-S3(beta2) chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -10,6 +11,7 @@ from __future__ import division, print_function from ..mem_definition_base import EfuseBlocksBase, EfuseFieldsBase, EfuseRegistersBase +# fmt: off class EfuseDefineRegisters(EfuseRegistersBase): EFUSE_ADDR_MASK = 0x00000FFF @@ -239,3 +241,4 @@ class EfuseDefineFields(EfuseFieldsBase): ('ADC2_MODE2_D1', "calibration", 2, 7, 20, "uint:6", 21, None, "adc_tp", "??? ADC2 calibration 15", None), ('ADC2_MODE3_D1', "calibration", 2, 7, 26, "uint:6", 21, None, "adc_tp", "??? ADC2 calibration 16", None), ] +# fmt: on diff --git a/espefuse/efuse/esp32s3beta2/operations.py b/espefuse/efuse/esp32s3beta2/operations.py index e924061..0225c72 100644 --- a/espefuse/efuse/esp32s3beta2/operations.py +++ b/espefuse/efuse/esp32s3beta2/operations.py @@ -17,58 +17,162 @@ import esptool from . import fields from .. import util -from ..base_operations import (add_common_commands, add_force_write_always, burn_bit, burn_block_data, # noqa: F401 - burn_efuse, check_error, dump, read_protect_efuse, summary, write_protect_efuse) # noqa: F401 +from ..base_operations import ( + add_common_commands, + add_force_write_always, + 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') + 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') + 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) - 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) + 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.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') + 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) - 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) + 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) + 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( + "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")) + 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.') + p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") def burn_custom_mac(esp, efuses, args): @@ -89,35 +193,38 @@ def set_flash_voltage(esp, efuses, args): 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 == "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 == "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. -The following efuse is burned: VDD_SPI_FORCE. -It is possible to later re-enable the internal regulator (%s) by burning an additional efuse -""" % ("to 3.3V" if sdio_tieh.get() != 0 else "to 1.8V or 3.3V") - elif args.voltage == '1.8V': - msg = """ -Set internal flash voltage regulator (VDD_SPI) to 1.8V. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD. -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. -The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. -""" + 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_force.save(1) # Disable GPIO45 + if args.voltage != "OFF": sdio_reg.save(1) # Enable internal regulator - if args.voltage == '3.3V': + if args.voltage == "3.3V": sdio_tieh.save(1) print("VDD_SPI setting complete.") if not efuses.burn_all(check_batch_mode=True): @@ -127,6 +234,7 @@ The following efuses are burned: VDD_SPI_FORCE, VDD_SPI_XPD, VDD_SPI_TIEH. def adc_info(esp, efuses, args): print("") + # fmt: off if efuses["BLOCK2_VERSION"].get() == 1: print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) @@ -159,24 +267,38 @@ def adc_info(esp, efuses, args): print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) else: print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].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]):] + 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]):] + 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]):] + 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))) + 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): + 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: @@ -202,8 +324,10 @@ def burn_key(esp, efuses, args, digest=None): 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)) + 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 @@ -211,18 +335,29 @@ def burn_key(esp, efuses, args, digest=None): 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. + # 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)) + 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)) + 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(): @@ -253,8 +388,12 @@ def burn_key(esp, efuses, args, digest=None): 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]):] + 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: @@ -265,27 +404,29 @@ def burn_key_digest(esp, efuses, args): 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)) + 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') + 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': + 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': + if cmd_line_args.operation == "execute_scripts": cmd_line_args.configfiles = configfiles cmd_line_args.index = index if cmd_line_args.operation is None: @@ -303,8 +444,8 @@ def execute_scripts(esp, efuses, args): del args.scripts for file in scripts: - with open(file.name, 'r') as file: - exec(compile(file.read(), file.name, 'exec')) + with open(file.name, "r") as file: + exec(compile(file.read(), file.name, "exec")) if args.debug: for block in efuses.blocks: diff --git a/espefuse/efuse/mem_definition_base.py b/espefuse/efuse/mem_definition_base.py index cf6df86..2f5fc00 100644 --- a/espefuse/efuse/mem_definition_base.py +++ b/espefuse/efuse/mem_definition_base.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file describes eFuses fields and registers for ESP32 chip # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -12,11 +13,11 @@ from collections import namedtuple class EfuseRegistersBase(object): # Coding Scheme values - CODING_SCHEME_NONE = 0 - CODING_SCHEME_34 = 1 - CODING_SCHEME_REPEAT = 2 + CODING_SCHEME_NONE = 0 + CODING_SCHEME_34 = 1 + CODING_SCHEME_REPEAT = 2 CODING_SCHEME_NONE_RECOVERY = 3 - CODING_SCHEME_RS = 4 + CODING_SCHEME_RS = 4 EFUSE_BURN_TIMEOUT = 0.250 # seconds @@ -24,7 +25,11 @@ class EfuseRegistersBase(object): class EfuseBlocksBase(object): BLOCKS = None - NamedtupleBlock = namedtuple('Block', 'name alias id rd_addr wr_addr write_disable_bit read_disable_bit len key_purpose') + NamedtupleBlock = namedtuple( + "Block", + "name alias id rd_addr wr_addr write_disable_bit " + "read_disable_bit len key_purpose", + ) @staticmethod def get(tuple_block): @@ -45,7 +50,11 @@ class EfuseBlocksBase(object): class EfuseFieldsBase(object): - NamedtupleField = namedtuple('Efuse', 'name category block word pos type write_disable_bit read_disable_bit class_type description dictionary') + NamedtupleField = namedtuple( + "Efuse", + "name category block word pos type write_disable_bit " + "read_disable_bit class_type description dictionary", + ) @staticmethod def get(tuple_field): diff --git a/espefuse/efuse/util.py b/espefuse/efuse/util.py index ee2eadf..5972fd4 100644 --- a/espefuse/efuse/util.py +++ b/espefuse/efuse/util.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# # This file consists of the common useful functions for eFuse # # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD @@ -19,14 +20,16 @@ def hexify(bitstring, separator=""): def popcnt(b): - """ Return number of "1" bits set in 'b' """ + """Return number of "1" bits set in 'b'""" return len([x for x in bin(b) if x == "1"]) def check_duplicate_name_in_list(name_list): duples_name = [name for i, name in enumerate(name_list) if name in name_list[:i]] if duples_name != []: - raise esptool.FatalError("Found repeated {} in the name list".format(duples_name)) + raise esptool.FatalError( + "Found repeated {} in the name list".format(duples_name) + ) class SdkConfig(object): @@ -34,13 +37,15 @@ class SdkConfig(object): self.sdkconfig = dict() if path_to_file is None: return - with open(path_to_file, 'r') as file: + with open(path_to_file, "r") as file: for line in file.readlines(): if line.startswith("#"): continue - config = line.strip().split('=', 1) + config = line.strip().split("=", 1) if len(config) == 2: - self.sdkconfig[config[0]] = True if config[1] == 'y' else config[1].strip('"') + self.sdkconfig[config[0]] = ( + True if config[1] == "y" else config[1].strip('"') + ) def __getitem__(self, config_name): try: diff --git a/espsecure.py b/espsecure.py index a4a50a2..4d1698f 100755 --- a/espsecure.py +++ b/espsecure.py @@ -1,15 +1,18 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -# This executable script is a thin wrapper around the main functionality in the espsecure Python package +# This executable script is a thin wrapper around the main functionality +# in the espsecure Python package # -# If esptool (with espefuse and espsecure) is installed via setup.py or pip then this file is not used at all, -# it's compatibility for the older "run from source dir" esptool approach. +# If esptool (with espefuse and espsecure) is installed via setup.py or pip +# then this file is not used at all, +# it's compatibility for the older "run from source dir" espsecure approach. import espsecure -if __name__ == '__main__': +if __name__ == "__main__": espsecure._main() diff --git a/espsecure/__init__.py b/espsecure/__init__.py index 86b47e9..5b4bc9a 100755 --- a/espsecure/__init__.py +++ b/espsecure/__init__.py @@ -46,26 +46,26 @@ CURVE_ID_P256 = 2 def get_chunks(source, chunk_len): - """ Returns an iterator over 'chunk_len' chunks of 'source' """ - return (source[i: i + chunk_len] for i in range(0, len(source), chunk_len)) + """Returns an iterator over 'chunk_len' chunks of 'source'""" + return (source[i : i + chunk_len] for i in range(0, len(source), chunk_len)) def endian_swap_words(source): - """ Endian-swap each word in 'source' bitstring """ + """Endian-swap each word in 'source' bitstring""" assert len(source) % 4 == 0 words = "I" * (len(source) // 4) return struct.pack("<" + words, *struct.unpack(">" + words, source)) def swap_word_order(source): - """ Swap the order of the words in 'source' bitstring """ + """Swap the order of the words in 'source' bitstring""" assert len(source) % 4 == 0 words = "I" * (len(source) // 4) return struct.pack(words, *reversed(struct.unpack(words, source))) def _load_hardware_key(keyfile): - """ Load a 128/256/512-bit key, similar to stored in efuse, from a file + """Load a 128/256/512-bit key, similar to stored in efuse, from a file 128-bit keys will be extended to 256-bit using the SHA256 of the key 192-bit keys will be extended to 256-bit using the same algorithm used @@ -73,7 +73,10 @@ def _load_hardware_key(keyfile): """ key = keyfile.read() if len(key) not in [16, 24, 32, 64]: - raise esptool.FatalError("Key file contains wrong length (%d bytes), 16, 24, 32 or 64 expected." % len(key)) + raise esptool.FatalError( + "Key file contains wrong length (%d bytes), 16, 24, 32 or 64 expected." + % len(key) + ) if len(key) == 16: key = _sha256_digest(key) print("Using 128-bit key (extended)") @@ -89,9 +92,9 @@ def _load_hardware_key(keyfile): def digest_secure_bootloader(args): - """ Calculate the digest of a bootloader image, in the same way the hardware + """Calculate the digest of a bootloader image, in the same way the hardware secure boot engine would do so. Can be used with a pre-loaded key to update a - secure bootloader. """ + secure bootloader.""" _check_output_is_not_input(args.keyfile, args.output) _check_output_is_not_input(args.image, args.output) _check_output_is_not_input(args.iv, args.output) @@ -148,7 +151,7 @@ def digest_secure_bootloader(args): digest = digest.digest() for word in get_chunks(digest, 4): f.write(word[::-1]) # swap word order in the result - f.write(b'\xFF' * (0x1000 - f.tell())) # pad to 0x1000 + f.write(b"\xFF" * (0x1000 - f.tell())) # pad to 0x1000 f.write(plaintext_image) print("digest+image written to %s" % args.output) @@ -166,56 +169,77 @@ def generate_signing_key(args): if hasattr(args, "scheme"): if args.scheme != "ecdsa256": raise esptool.FatalError("ERROR: V1 only supports ECDSA256") - """ Generate an ECDSA signing key for signing secure boot images (post-bootloader) """ + """ + Generate an ECDSA signing key for signing secure boot images (post-bootloader) + """ _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile) print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile) elif args.version == "2": - if (args.scheme == "rsa3072" or args.scheme is None): - """ Generate a RSA 3072 signing key for signing secure boot images """ + if args.scheme == "rsa3072" or args.scheme is None: + """Generate a RSA 3072 signing key for signing secure boot images""" private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=3072, - backend=default_backend() + public_exponent=65537, key_size=3072, backend=default_backend() ).private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() + encryption_algorithm=serialization.NoEncryption(), ) with open(args.keyfile, "wb") as f: f.write(private_key) print("RSA 3072 private key in PEM format written to %s" % args.keyfile) - elif (args.scheme == "ecdsa192"): - """ Generate a ECDSA 192 signing key for signing secure boot images """ + elif args.scheme == "ecdsa192": + """Generate a ECDSA 192 signing key for signing secure boot images""" _generate_ecdsa_signing_key(ecdsa.NIST192p, args.keyfile) - print("ECDSA NIST192p private key in PEM format written to %s" % args.keyfile) - elif (args.scheme == "ecdsa256"): - """ Generate a ECDSA 256 signing key for signing secure boot images """ + print( + "ECDSA NIST192p private key in PEM format written to %s" % args.keyfile + ) + elif args.scheme == "ecdsa256": + """Generate a ECDSA 256 signing key for signing secure boot images""" _generate_ecdsa_signing_key(ecdsa.NIST256p, args.keyfile) - print("ECDSA NIST256p private key in PEM format written to %s" % args.keyfile) + print( + "ECDSA NIST256p private key in PEM format written to %s" % args.keyfile + ) else: - raise esptool.FatalError("ERROR: Unsupported signing scheme (%s)" % args.scheme) + raise esptool.FatalError( + "ERROR: Unsupported signing scheme (%s)" % args.scheme + ) def _load_ecdsa_signing_key(keyfile): - """ Load ECDSA signing key for Secure Boot V1 only """ + """Load ECDSA signing key for Secure Boot V1 only""" sk = ecdsa.SigningKey.from_pem(keyfile.read()) if sk.curve != ecdsa.NIST256p: - raise esptool.FatalError("Signing key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1") + raise esptool.FatalError( + "Signing key uses incorrect curve. ESP32 Secure Boot only supports " + "NIST256p (openssl calls this curve 'prime256v1" + ) return sk def _load_sbv2_signing_key(keydata): """ - Load Secure Boot V2 signing key, can be rsa.RSAPrivateKey or ec.EllipticCurvePrivateKey + Load Secure Boot V2 signing key + + can be rsa.RSAPrivateKey or ec.EllipticCurvePrivateKey """ - sk = serialization.load_pem_private_key(keydata, password=None, backend=default_backend()) + sk = serialization.load_pem_private_key( + keydata, password=None, backend=default_backend() + ) if isinstance(sk, rsa.RSAPrivateKey): if sk.key_size != 3072: - raise esptool.FatalError("Key file has length %d bits. Secure boot v2 only supports RSA-3072." % sk.key_size) + raise esptool.FatalError( + "Key file has length %d bits. Secure boot v2 only supports RSA-3072." + % sk.key_size + ) return sk if isinstance(sk, ec.EllipticCurvePrivateKey): - if not (isinstance(sk.curve, ec.SECP192R1) or isinstance(sk.curve, ec.SECP256R1)): - raise esptool.FatalError("Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports NIST192p, NIST256p (aka prime192v1, prime256v1)") + if not ( + isinstance(sk.curve, ec.SECP192R1) or isinstance(sk.curve, ec.SECP256R1) + ): + raise esptool.FatalError( + "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " + "NIST192p, NIST256p (aka prime192v1, prime256v1)" + ) return sk raise esptool.FatalError("Unsupported signing key for Secure Boot V2") @@ -228,11 +252,19 @@ def _load_sbv2_pub_key(keydata): vk = serialization.load_pem_public_key(keydata, backend=default_backend()) if isinstance(vk, rsa.RSAPublicKey): if vk.key_size != 3072: - raise esptool.FatalError("Key file has length %d bits. Secure boot v2 only supports RSA-3072." % vk.key_size) + raise esptool.FatalError( + "Key file has length %d bits. Secure boot v2 only supports RSA-3072." + % vk.key_size + ) return vk if isinstance(vk, ec.EllipticCurvePublicKey): - if not (isinstance(vk.curve, ec.SECP192R1) or isinstance(vk.curve, ec.SECP256R1)): - raise esptool.FatalError("Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports NIST192p, NIST256p (aka prime192v1, prime256v1)") + if not ( + isinstance(vk.curve, ec.SECP192R1) or isinstance(vk.curve, ec.SECP256R1) + ): + raise esptool.FatalError( + "Key file uses incorrect curve. Secure Boot V2 + ECDSA only supports " + "NIST192p, NIST256p (aka prime192v1, prime256v1)" + ) return vk raise esptool.FatalError("Unsupported public key for Secure Boot V2") @@ -245,19 +277,22 @@ def _get_sbv2_pub_key(keyfile): elif b"-BEGIN PUBLIC KEY" in key_data: vk = _load_sbv2_pub_key(key_data) else: - raise esptool.FatalError("Verification key does not appear to be an RSA Private or Public key in PEM format. Unsupported") + raise esptool.FatalError( + "Verification key does not appear to be an RSA Private or " + "Public key in PEM format. Unsupported" + ) return vk def _get_sbv2_rsa_primitives(public_key): - primitives = namedtuple('primitives', ['n', 'e', 'm', 'rinv']) + primitives = namedtuple("primitives", ["n", "e", "m", "rinv"]) numbers = public_key.public_numbers() primitives.n = numbers.n # primitives.e = numbers.e # two public key components # Note: this cheats and calls a private 'rsa' method to get the modular # inverse calculation. - primitives.m = - rsa._modinv(primitives.n, 1 << 32) + primitives.m = -rsa._modinv(primitives.n, 1 << 32) rr = 1 << (public_key.key_size * 2) primitives.rinv = rr % primitives.n @@ -266,25 +301,30 @@ def _get_sbv2_rsa_primitives(public_key): def _microecc_format(a, b): """ - Given two numbers (curve coordinates or (r,s) signature), write them out as a little-endian byte sequence suitable for micro-ecc + Given two numbers (curve coordinates or (r,s) signature), write them out as a + little-endian byte sequence suitable for micro-ecc "native little endian" mode """ ab = int_to_bytes(a)[::-1] + int_to_bytes(b)[::-1] - assert len(ab) == 48 or len(ab) == 64, "a, b may need to be byte padded to either be 48 or 64 bytes in length" + assert ( + len(ab) == 48 or len(ab) == 64 + ), "a, b may need to be byte padded to either be 48 or 64 bytes in length" return ab def sign_data(args): _check_output_is_not_input(args.keyfile, args.output) _check_output_is_not_input(args.datafile, args.output) - if args.version == '1': + if args.version == "1": return sign_secure_boot_v1(args) - elif args.version == '2': + elif args.version == "2": return sign_secure_boot_v2(args) def sign_secure_boot_v1(args): - """ Sign a data file with a ECDSA private key, append binary signature to file contents """ + """ + Sign a data file with a ECDSA private key, append binary signature to file contents + """ if len(args.keyfile) > 1: raise esptool.FatalError("Secure Boot V1 only supports one signing key") sk = _load_ecdsa_signing_key(args.keyfile[0]) @@ -297,20 +337,29 @@ def sign_secure_boot_v1(args): vk = sk.get_verifying_key() vk.verify(signature, binary_content, hashlib.sha256) # throws exception on failure - if args.output is None or os.path.abspath(args.output) == os.path.abspath(args.datafile.name): # append signature to input file + if args.output is None or os.path.abspath(args.output) == os.path.abspath( + args.datafile.name + ): # append signature to input file args.datafile.close() outfile = open(args.datafile.name, "ab") else: # write file & signature to new file outfile = open(args.output, "wb") outfile.write(binary_content) - outfile.write(struct.pack("I", 0)) # Version indicator, allow for different curves/formats later + outfile.write( + struct.pack("I", 0) + ) # Version indicator, allow for different curves/formats later outfile.write(signature) outfile.close() - print("Signed %d bytes of data from %s with key %s" % (len(binary_content), args.datafile.name, args.keyfile[0].name)) + print( + "Signed %d bytes of data from %s with key %s" + % (len(binary_content), args.datafile.name, args.keyfile[0].name) + ) def sign_secure_boot_v2(args): - """ Sign a firmware app image with an RSA private key using RSA-PSS, or ECDSA private key using P192 or P256. + """ + Sign a firmware app image with an RSA private key using RSA-PSS, + or ECDSA private key using P192 or P256. Write output file with a Secure Boot V2 header appended. """ @@ -323,12 +372,19 @@ def sign_secure_boot_v2(args): contents = args.datafile.read() if key_count > SIG_BLOCK_MAX_COUNT: - print("WARNING: Upto %d signing keys are supported for ESP32-S2. For ESP32-ECO3 only 1 signing key is supported", SIG_BLOCK_MAX_COUNT) + print( + "WARNING: Upto %d signing keys are supported for ESP32-S2. " + "For ESP32-ECO3 only 1 signing key is supported", + SIG_BLOCK_MAX_COUNT, + ) if len(contents) % SECTOR_SIZE != 0: pad_by = SECTOR_SIZE - (len(contents) % SECTOR_SIZE) - print("Padding data contents by %d bytes so signature sector aligns at sector boundary" % pad_by) - contents += b'\xff' * pad_by + print( + "Padding data contents by %d bytes " + "so signature sector aligns at sector boundary" % pad_by + ) + contents += b"\xff" * pad_by elif args.append_signatures: sig_block_num = 0 @@ -336,21 +392,33 @@ def sign_secure_boot_v2(args): sig_block = validate_signature_block(contents, sig_block_num) if sig_block is None: break - signature_sector += sig_block # Signature sector is populated with already valid blocks + signature_sector += ( + sig_block # Signature sector is populated with already valid blocks + ) sig_block_num += 1 assert len(signature_sector) % SIG_BLOCK_SIZE == 0 if sig_block_num == 0: - print("No valid signature blocks found. Discarding --append-signature and proceeding to sign the image afresh.") + print( + "No valid signature blocks found. " + "Discarding --append-signature and proceeding to sign the image afresh." + ) else: - print("%d valid signature block(s) already present in the signature sector." % sig_block_num) + print( + "%d valid signature block(s) already present in the signature sector." + % sig_block_num + ) empty_signature_blocks = SIG_BLOCK_MAX_COUNT - sig_block_num if key_count > empty_signature_blocks: - raise esptool.FatalError("Number of keys(%d) more than the empty signature blocks.(%d)" % (key_count, empty_signature_blocks)) - - contents = contents[:len(contents) - SECTOR_SIZE] # Signature stripped off the content (the legitimate blocks are included in signature_sector) + raise esptool.FatalError( + "Number of keys(%d) more than the empty signature blocks.(%d)" + % (key_count, empty_signature_blocks) + ) + # Signature stripped off the content + # (the legitimate blocks are included in signature_sector) + contents = contents[: len(contents) - SECTOR_SIZE] print("%d signing key(s) found." % key_count) # Calculate digest of data file @@ -370,7 +438,7 @@ def sign_secure_boot_v2(args): mgf=padding.MGF1(hashes.SHA256()), salt_length=32, ), - utils.Prehashed(hashes.SHA256()) + utils.Prehashed(hashes.SHA256()), ) rsa_primitives = _get_sbv2_rsa_primitives(private_key.public_key()) @@ -380,19 +448,22 @@ def sign_secure_boot_v2(args): # values (signatures, coefficients) to little endian # for use with the RSA peripheral, rather than big endian # which is conventionally used for RSA. - signature_block = struct.pack(" 0 and len(signature_sector) <= SIG_BLOCK_SIZE * 3 and len(signature_sector) % SIG_BLOCK_SIZE == 0 + assert ( + len(signature_sector) > 0 + and len(signature_sector) <= SIG_BLOCK_SIZE * 3 + and len(signature_sector) % SIG_BLOCK_SIZE == 0 + ) total_sig_blocks = len(signature_sector) // SIG_BLOCK_SIZE # Pad signature_sector to sector - signature_sector = signature_sector + \ - (b'\xff' * (SECTOR_SIZE - len(signature_sector))) + signature_sector = signature_sector + ( + b"\xff" * (SECTOR_SIZE - len(signature_sector)) + ) assert len(signature_sector) == SECTOR_SIZE # Write to output file, or append to existing file @@ -429,18 +510,21 @@ def sign_secure_boot_v2(args): args.output = args.datafile.name with open(args.output, "wb") as f: f.write(contents + signature_sector) - print("Signed %d bytes of data from %s. Signature sector now has %d signature blocks." % (len(contents), args.datafile.name, total_sig_blocks)) + print( + "Signed %d bytes of data from %s. Signature sector now has %d signature blocks." + % (len(contents), args.datafile.name, total_sig_blocks) + ) def verify_signature(args): - if args.version == '1': + if args.version == "1": return verify_signature_v1(args) - elif args.version == '2': + elif args.version == "2": return verify_signature_v2(args) def verify_signature_v1(args): - """ Verify a previously signed binary image, using the ECDSA public key """ + """Verify a previously signed binary image, using the ECDSA public key""" key_data = args.keyfile.read() if b"-BEGIN EC PRIVATE KEY" in key_data: sk = ecdsa.SigningKey.from_pem(key_data) @@ -448,19 +532,27 @@ def verify_signature_v1(args): elif b"-BEGIN PUBLIC KEY" in key_data: vk = ecdsa.VerifyingKey.from_pem(key_data) elif len(key_data) == 64: - vk = ecdsa.VerifyingKey.from_string(key_data, - curve=ecdsa.NIST256p) + vk = ecdsa.VerifyingKey.from_string(key_data, curve=ecdsa.NIST256p) else: - raise esptool.FatalError("Verification key does not appear to be an EC key in PEM format or binary EC public key data. Unsupported") + raise esptool.FatalError( + "Verification key does not appear to be an EC key in PEM format " + "or binary EC public key data. Unsupported" + ) if vk.curve != ecdsa.NIST256p: - raise esptool.FatalError("Public key uses incorrect curve. ESP32 Secure Boot only supports NIST256p (openssl calls this curve 'prime256v1") + raise esptool.FatalError( + "Public key uses incorrect curve. ESP32 Secure Boot only supports " + "NIST256p (openssl calls this curve 'prime256v1" + ) binary_content = args.datafile.read() data = binary_content[0:-68] sig_version, signature = struct.unpack("I64s", binary_content[-68:]) if sig_version != 0: - raise esptool.FatalError("Signature block has version %d. This version of espsecure only supports version 0." % sig_version) + raise esptool.FatalError( + "Signature block has version %d. This version of espsecure " + "only supports version 0." % sig_version + ) print("Verifying %d bytes of data" % len(data)) try: if vk.verify(signature, data, hashlib.sha256): @@ -473,31 +565,39 @@ def verify_signature_v1(args): def validate_signature_block(image_content, sig_blk_num): SECTOR_SIZE = 4096 - SIG_BLOCK_SIZE = 1216 # Refer to secure boot v2 signature block format for more details. + SIG_BLOCK_SIZE = ( + 1216 # Refer to secure boot v2 signature block format for more details. + ) offset = -SECTOR_SIZE + sig_blk_num * SIG_BLOCK_SIZE - sig_blk = image_content[offset: offset + SIG_BLOCK_SIZE] - assert(len(sig_blk) == SIG_BLOCK_SIZE) + sig_blk = image_content[offset : offset + SIG_BLOCK_SIZE] + assert len(sig_blk) == SIG_BLOCK_SIZE - # note: in case of ECDSA key, the exact fields in the middle are wrong (but unused here) - magic, version, _, _, _, _, _, _, blk_crc = struct.unpack("> 5 key ^= ((mul1 * addr) | ((mul2 * addr) & mul2_mask)) & tweak_range - return int.to_bytes(key, length=32, byteorder='big', signed=False) + return int.to_bytes(key, length=32, byteorder="big", signed=False) def generate_flash_encryption_key(args): @@ -795,11 +961,15 @@ def generate_flash_encryption_key(args): args.key_file.write(os.urandom(args.keylen // 8)) -def _flash_encryption_operation_esp32(output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt): +def _flash_encryption_operation_esp32( + output_file, input_file, flash_address, keyfile, flash_crypt_conf, do_decrypt +): key = _load_hardware_key(keyfile) if flash_address % 16 != 0: - raise esptool.FatalError("Starting flash address 0x%x must be a multiple of 16" % flash_address) + raise esptool.FatalError( + "Starting flash address 0x%x must be a multiple of 16" % flash_address + ) if flash_crypt_conf == 0: print("WARNING: Setting FLASH_CRYPT_CONF to zero is not recommended") @@ -808,7 +978,7 @@ def _flash_encryption_operation_esp32(output_file, input_file, flash_address, ke tweak_range = _flash_encryption_tweak_range(flash_crypt_conf) else: tweak_range = _flash_encryption_tweak_range_bits(flash_crypt_conf) - key = int.from_bytes(key, byteorder='big', signed=False) + key = int.from_bytes(key, byteorder="big", signed=False) backend = default_backend() @@ -823,10 +993,14 @@ def _flash_encryption_operation_esp32(output_file, input_file, flash_address, ke raise esptool.FatalError("Data length is not a multiple of 16 bytes") pad = 16 - len(block) block = block + os.urandom(pad) - print("Note: Padding with %d bytes of random data (encrypted data must be multiple of 16 bytes long)" % pad) + print( + "Note: Padding with %d bytes of random data " + "(encrypted data must be multiple of 16 bytes long)" % pad + ) if block_offs % 32 == 0 or cipher is None: - # each bit of the flash encryption key is XORed with tweak bits derived from the offset of 32 byte block of flash + # each bit of the flash encryption key is XORed with tweak bits + # derived from the offset of 32 byte block of flash block_key = _flash_encryption_tweak_key(key, block_offs, tweak_range) if cipher is None: # first pass @@ -837,10 +1011,12 @@ def _flash_encryption_operation_esp32(output_file, input_file, flash_address, ke # versa. (This does not weaken AES.) actor = cipher.encryptor() if do_decrypt else cipher.decryptor() else: - # performance hack: changing the key using pyca-cryptography API requires recreating - # 'actor'. With openssl backend, this re-initializes the openssl cipher context. To save some time, - # manually call EVP_CipherInit_ex() in the openssl backend to update the key. - # If it fails, fall back to recreating the entire context via public API. + # performance hack: changing the key using pyca-cryptography API + # requires recreating'actor'. + # With openssl backend, this re-initializes the openssl cipher context. + # To save some time, manually call EVP_CipherInit_ex() in the openssl + # backend to update the key. + # If it fails, fall back to recreating the entire context via public API try: backend = actor._ctx._backend res = backend._lib.EVP_CipherInit_ex( @@ -853,7 +1029,8 @@ def _flash_encryption_operation_esp32(output_file, input_file, flash_address, ke ) backend.openssl_assert(res != 0) except AttributeError: - # backend is not an openssl backend, or implementation has changed: fall back to the slow safe version + # backend is not an openssl backend, or implementation has changed: + # fall back to the slow safe version cipher.algorithm.key = block_key actor = cipher.encryptor() if do_decrypt else cipher.decryptor() @@ -864,7 +1041,9 @@ def _flash_encryption_operation_esp32(output_file, input_file, flash_address, ke block_offs += 16 -def _flash_encryption_operation_aes_xts(output_file, input_file, flash_address, keyfile, do_decrypt): +def _flash_encryption_operation_aes_xts( + output_file, input_file, flash_address, keyfile, do_decrypt +): """ Apply the AES-XTS algorithm with the hardware addressing scheme used by Espressif @@ -881,10 +1060,14 @@ def _flash_encryption_operation_aes_xts(output_file, input_file, flash_address, indata = input_file.read() if flash_address % 16 != 0: - raise esptool.FatalError("Starting flash address 0x%x must be a multiple of 16" % flash_address) + raise esptool.FatalError( + "Starting flash address 0x%x must be a multiple of 16" % flash_address + ) if len(indata) % 16 != 0: - raise esptool.FatalError("Input data length (%d) must be a multiple of 16" % len(indata)) + raise esptool.FatalError( + "Input data length (%d) must be a multiple of 16" % len(indata) + ) if len(indata) == 0: raise esptool.FatalError("Input data must be longer than 0") @@ -904,17 +1087,19 @@ def _flash_encryption_operation_aes_xts(output_file, input_file, flash_address, output = b"" for inblock in inblocks: # for each block tweak = struct.pack(" 0: yield text[0:block_len] @@ -941,18 +1130,36 @@ def decrypt_flash_data(args): _check_output_is_not_input(args.keyfile, args.output) _check_output_is_not_input(args.encrypted_file, args.output) if args.aes_xts: - return _flash_encryption_operation_aes_xts(args.output, args.encrypted_file, args.address, args.keyfile, True) + return _flash_encryption_operation_aes_xts( + args.output, args.encrypted_file, args.address, args.keyfile, True + ) else: - return _flash_encryption_operation_esp32(args.output, args.encrypted_file, args.address, args.keyfile, args.flash_crypt_conf, True) + return _flash_encryption_operation_esp32( + args.output, + args.encrypted_file, + args.address, + args.keyfile, + args.flash_crypt_conf, + True, + ) def encrypt_flash_data(args): _check_output_is_not_input(args.keyfile, args.output) _check_output_is_not_input(args.plaintext_file, args.output) if args.aes_xts: - return _flash_encryption_operation_aes_xts(args.output, args.plaintext_file, args.address, args.keyfile, False) + return _flash_encryption_operation_aes_xts( + args.output, args.plaintext_file, args.address, args.keyfile, False + ) else: - return _flash_encryption_operation_esp32(args.output, args.plaintext_file, args.address, args.keyfile, args.flash_crypt_conf, False) + return _flash_encryption_operation_esp32( + args.output, + args.plaintext_file, + args.address, + args.keyfile, + args.flash_crypt_conf, + False, + ) def _samefile(p1, p2): @@ -961,28 +1168,42 @@ def _samefile(p1, p2): except (OSError, AttributeError): # AttributeError - Python 2.7 on Windows doesn't know os.path.samefile() # OSError (FileNotFoundError under Python 3) - return os.path.normcase(os.path.normpath(p1)) == os.path.normcase(os.path.normpath(p2)) + return os.path.normcase(os.path.normpath(p1)) == os.path.normcase( + os.path.normpath(p2) + ) def _check_output_is_not_input(input_file, output_file): - i = getattr(input_file, 'name', input_file) - o = getattr(output_file, 'name', output_file) - # i & o should be string containing the path to files if espsecure was invoked from command line - # i & o still can be something else when espsecure was imported and the functions used directly (e.g. io.BytesIO()) - check_f = _samefile if isinstance(i, _string_type) and isinstance(o, _string_type) else operator.eq + i = getattr(input_file, "name", input_file) + o = getattr(output_file, "name", output_file) + # i & o should be string containing the path to files if espsecure + # was invoked from command line + # i & o still can be something else when espsecure was imported + # and the functions used directly (e.g. io.BytesIO()) + check_f = ( + _samefile + if isinstance(i, _string_type) and isinstance(o, _string_type) + else operator.eq + ) if check_f(i, o): - raise esptool.FatalError('The input "{}" and output "{}" should not be the same!'.format(i, o)) + raise esptool.FatalError( + 'The input "{}" and output "{}" should not be the same!'.format(i, o) + ) class OutFileType(object): """ - This class is a replacement of argparse.FileType('wb'). It doesn't create a file immediately but only during the - first write. This allows us to do some checking before, e.g. that we are not overwriting the input. + This class is a replacement of argparse.FileType('wb'). + It doesn't create a file immediately but only during thefirst write. + This allows us to do some checking before, + e.g. that we are not overwriting the input. argparse.FileType('w')('-') returns STDOUT but argparse.FileType('wb') is not. - The file object is not closed on failure just like in the case of argparse.FileType('w'). + The file object is not closed on failure + just like in the case of argparse.FileType('w'). """ + def __init__(self): self.path = None self.file_obj = None @@ -992,12 +1213,12 @@ class OutFileType(object): return self def __repr__(self): - return '{}({})'.format(type(self).__name__, self.path) + return "{}({})".format(type(self).__name__, self.path) def write(self, payload): if len(payload) > 0: if not self.file_obj: - self.file_obj = open(self.path, 'wb') + self.file_obj = open(self.path, "wb") self.file_obj.write(payload) def close(self): @@ -1014,110 +1235,340 @@ def main(custom_commandline=None): """ Main function for espsecure - 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']. + 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']. """ - parser = argparse.ArgumentParser(description='espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool' % esptool.__version__, prog='espsecure') + parser = argparse.ArgumentParser( + description="espsecure.py v%s - ESP32 Secure Boot & Flash Encryption tool" + % esptool.__version__, + prog="espsecure", + ) subparsers = parser.add_subparsers( - dest='operation', - help='Run espsecure {command} -h for additional help') + dest="operation", help="Run espsecure {command} -h for additional help" + ) - p = subparsers.add_parser('digest_secure_bootloader', - help='Take a bootloader binary image and a secure boot key, and output a combined digest+binary ' - 'suitable for flashing along with the precalculated secure boot key.') - p.add_argument('--keyfile', '-k', help="256 bit key for secure boot digest.", type=argparse.FileType('rb'), required=True) - p.add_argument('--output', '-o', help="Output file for signed digest image.") - p.add_argument('--iv', help="128 byte IV file. Supply a file for testing purposes only, if not supplied an IV will be randomly generated.", - type=argparse.FileType('rb')) - p.add_argument('image', help="Bootloader image file to calculate digest from", type=argparse.FileType('rb')) + p = subparsers.add_parser( + "digest_secure_bootloader", + help="Take a bootloader binary image and a secure boot key, " + "and output a combined digest+binary suitable for flashing along " + "with the precalculated secure boot key.", + ) + p.add_argument( + "--keyfile", + "-k", + help="256 bit key for secure boot digest.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument("--output", "-o", help="Output file for signed digest image.") + p.add_argument( + "--iv", + help="128 byte IV file. Supply a file for testing purposes only, " + "if not supplied an IV will be randomly generated.", + type=argparse.FileType("rb"), + ) + p.add_argument( + "image", + help="Bootloader image file to calculate digest from", + type=argparse.FileType("rb"), + ) - p = subparsers.add_parser('generate_signing_key', - help='Generate a private key for signing secure boot images as per the secure boot version. ' - 'Key file is generated in PEM format, ' - 'Secure Boot V1 - ECDSA NIST256p private key. ' - 'Secure Boot V2 - RSA 3072, ECDSA NIST256p, ECDSA NIST192p private key.') - p.add_argument('--version', '-v', help="Version of the secure boot signing scheme to use.", choices=["1", "2"], default="1") - p.add_argument('--scheme', '-s', help="Scheme of secure boot signing.", choices=["rsa3072", "ecdsa192", "ecdsa256"], required=False) - p.add_argument('keyfile', help="Filename for private key file (embedded public key)") + p = subparsers.add_parser( + "generate_signing_key", + help="Generate a private key for signing secure boot images " + "as per the secure boot version. " + "Key file is generated in PEM format, " + "Secure Boot V1 - ECDSA NIST256p private key. " + "Secure Boot V2 - RSA 3072, ECDSA NIST256p, ECDSA NIST192p private key.", + ) + p.add_argument( + "--version", + "-v", + help="Version of the secure boot signing scheme to use.", + choices=["1", "2"], + default="1", + ) + p.add_argument( + "--scheme", + "-s", + help="Scheme of secure boot signing.", + choices=["rsa3072", "ecdsa192", "ecdsa256"], + required=False, + ) + p.add_argument( + "keyfile", help="Filename for private key file (embedded public key)" + ) - p = subparsers.add_parser('sign_data', - help='Sign a data file for use with secure boot. Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) ' - 'or either RSA-PSS or ECDSA w/ SHA-256 (V2).') - p.add_argument('--version', '-v', help="Version of the secure boot signing scheme to use.", choices=["1", "2"], required=True) - p.add_argument('--keyfile', '-k', help="Private key file for signing. Key is in PEM format.", type=argparse.FileType('rb'), required=True, nargs='+') - p.add_argument('--append_signatures', '-a', help="Append signature block(s) to already signed image" - "Valid only for ESP32-S2.", action='store_true') - p.add_argument('--output', '-o', help="Output file for signed digest image. Default is to sign the input file.") - p.add_argument('datafile', help="File to sign. For version 1, this can be any file. For version 2, this must be a valid app image.", - type=argparse.FileType('rb')) + p = subparsers.add_parser( + "sign_data", + help="Sign a data file for use with secure boot. " + "Signing algorithm is deterministic ECDSA w/ SHA-512 (V1) " + "or either RSA-PSS or ECDSA w/ SHA-256 (V2).", + ) + p.add_argument( + "--version", + "-v", + help="Version of the secure boot signing scheme to use.", + choices=["1", "2"], + required=True, + ) + p.add_argument( + "--keyfile", + "-k", + help="Private key file for signing. Key is in PEM format.", + type=argparse.FileType("rb"), + required=True, + nargs="+", + ) + p.add_argument( + "--append_signatures", + "-a", + help="Append signature block(s) to already signed image" + "Valid only for ESP32-S2.", + action="store_true", + ) + p.add_argument( + "--output", + "-o", + help="Output file for signed digest image. Default is to sign the input file.", + ) + p.add_argument( + "datafile", + help="File to sign. For version 1, this can be any file. " + "For version 2, this must be a valid app image.", + type=argparse.FileType("rb"), + ) - p = subparsers.add_parser('verify_signature', - help='Verify a data file previously signed by "sign_data", using the public key.') - p.add_argument('--version', '-v', help="Version of the secure boot scheme to use.", choices=["1", "2"], required=True) - p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be private or public key in PEM format.", - type=argparse.FileType('rb'), required=True) - p.add_argument('datafile', help="Signed data file to verify signature.", type=argparse.FileType('rb')) + p = subparsers.add_parser( + "verify_signature", + help='Verify a data file previously signed by "sign_data", ' + "using the public key.", + ) + p.add_argument( + "--version", + "-v", + help="Version of the secure boot scheme to use.", + choices=["1", "2"], + required=True, + ) + p.add_argument( + "--keyfile", + "-k", + help="Public key file for verification. " + "Can be private or public key in PEM format.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument( + "datafile", + help="Signed data file to verify signature.", + type=argparse.FileType("rb"), + ) - p = subparsers.add_parser('extract_public_key', - help='Extract the public verification key for signatures, save it as a raw binary file.') - p.add_argument('--version', '-v', help="Version of the secure boot signing scheme to use.", choices=["1", "2"], default="1") - p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to extract the public verification key from.", type=argparse.FileType('rb'), - required=True) - p.add_argument('public_keyfile', help="File to save new public key into", type=OutFileType()) + p = subparsers.add_parser( + "extract_public_key", + help="Extract the public verification key for signatures, " + "save it as a raw binary file.", + ) + p.add_argument( + "--version", + "-v", + help="Version of the secure boot signing scheme to use.", + choices=["1", "2"], + default="1", + ) + p.add_argument( + "--keyfile", + "-k", + help="Private key file (PEM format) to extract the " + "public verification key from.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument( + "public_keyfile", help="File to save new public key into", type=OutFileType() + ) # Kept for compatibility purpose. We can deprecate this in a future release - p = subparsers.add_parser('digest_rsa_public_key', help='Generate an SHA-256 digest of the RSA public key. ' - 'This digest is burned into the eFuse and asserts the legitimacy of the public key for Secure boot v2.') - p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be private or public key in PEM format.", type=argparse.FileType('rb'), - required=True) - p.add_argument('--output', '-o', help="Output file for the digest.", required=True) + p = subparsers.add_parser( + "digest_rsa_public_key", + help="Generate an SHA-256 digest of the RSA public key. " + "This digest is burned into the eFuse and asserts the legitimacy " + "of the public key for Secure boot v2.", + ) + p.add_argument( + "--keyfile", + "-k", + help="Public key file for verification. " + "Can be private or public key in PEM format.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument("--output", "-o", help="Output file for the digest.", required=True) - p = subparsers.add_parser('digest_sbv2_public_key', help='Generate an SHA-256 digest of the public key. ' - 'This digest is burned into the eFuse and asserts the legitimacy of the public key for Secure boot v2.') - p.add_argument('--keyfile', '-k', help="Public key file for verification. Can be private or public key in PEM format.", type=argparse.FileType('rb'), - required=True) - p.add_argument('--output', '-o', help="Output file for the digest.", required=True) + p = subparsers.add_parser( + "digest_sbv2_public_key", + help="Generate an SHA-256 digest of the public key. " + "This digest is burned into the eFuse and asserts the legitimacy " + "of the public key for Secure boot v2.", + ) + p.add_argument( + "--keyfile", + "-k", + help="Public key file for verification. " + "Can be private or public key in PEM format.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument("--output", "-o", help="Output file for the digest.", required=True) - p = subparsers.add_parser('signature_info_v2', help='Reads the signature block and provides the signature block information.') - p.add_argument('datafile', help="Secure boot v2 signed data file.", type=argparse.FileType('rb')) + p = subparsers.add_parser( + "signature_info_v2", + help="Reads the signature block and provides the signature block information.", + ) + p.add_argument( + "datafile", + help="Secure boot v2 signed data file.", + type=argparse.FileType("rb"), + ) - p = subparsers.add_parser('digest_private_key', help='Generate an SHA-256 digest of the private signing key. ' - 'This can be used as a reproducible secure bootloader or flash encryption key.') - p.add_argument('--keyfile', '-k', help="Private key file (PEM format) to generate a digest from.", type=argparse.FileType('rb'), - required=True) - p.add_argument('--keylen', '-l', help="Length of private key digest file to generate (in bits). 3/4 Coding Scheme requires 192 bit key.", - choices=[192, 256], default=256, type=int) - p.add_argument('digest_file', help="File to write 32 byte digest into", type=OutFileType()) + p = subparsers.add_parser( + "digest_private_key", + help="Generate an SHA-256 digest of the private signing key. " + "This can be used as a reproducible secure bootloader or flash encryption key.", + ) + p.add_argument( + "--keyfile", + "-k", + help="Private key file (PEM format) to generate a digest from.", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument( + "--keylen", + "-l", + help="Length of private key digest file to generate (in bits). " + "3/4 Coding Scheme requires 192 bit key.", + choices=[192, 256], + default=256, + type=int, + ) + p.add_argument( + "digest_file", help="File to write 32 byte digest into", type=OutFileType() + ) - p = subparsers.add_parser('generate_flash_encryption_key', help='Generate a development-use flash encryption key with random data.') - p.add_argument('--keylen', '-l', help="Length of private key digest file to generate (in bits). 3/4 Coding Scheme requires 192 bit key.", - choices=[128, 192, 256, 512], default=256, type=int) - p.add_argument('key_file', help="File to write 16, 24, 32 or 64 byte key into", type=OutFileType()) + p = subparsers.add_parser( + "generate_flash_encryption_key", + help="Generate a development-use flash encryption key with random data.", + ) + p.add_argument( + "--keylen", + "-l", + help="Length of private key digest file to generate (in bits). " + "3/4 Coding Scheme requires 192 bit key.", + choices=[128, 192, 256, 512], + default=256, + type=int, + ) + p.add_argument( + "key_file", + help="File to write 16, 24, 32 or 64 byte key into", + type=OutFileType(), + ) - p = subparsers.add_parser('decrypt_flash_data', help='Decrypt some data read from encrypted flash (using known key)') - p.add_argument('encrypted_file', help="File with encrypted flash contents", type=argparse.FileType('rb')) - p.add_argument('--aes_xts', '-x', help="Decrypt data using AES-XTS as used on ESP32-S2, ESP32-C2 and ESP32-C3", action='store_true') - p.add_argument('--keyfile', '-k', help="File with flash encryption key", type=argparse.FileType('rb'), - required=True) - p.add_argument('--output', '-o', help="Output file for plaintext data.", type=OutFileType(), - required=True) - p.add_argument('--address', '-a', help="Address offset in flash that file was read from.", required=True, type=esptool.arg_auto_int) - p.add_argument('--flash_crypt_conf', help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", required=False, default=0xF, type=esptool.arg_auto_int) + p = subparsers.add_parser( + "decrypt_flash_data", + help="Decrypt some data read from encrypted flash (using known key)", + ) + p.add_argument( + "encrypted_file", + help="File with encrypted flash contents", + type=argparse.FileType("rb"), + ) + p.add_argument( + "--aes_xts", + "-x", + help="Decrypt data using AES-XTS as used on ESP32-S2, ESP32-C2 and ESP32-C3", + action="store_true", + ) + p.add_argument( + "--keyfile", + "-k", + help="File with flash encryption key", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument( + "--output", + "-o", + help="Output file for plaintext data.", + type=OutFileType(), + required=True, + ) + p.add_argument( + "--address", + "-a", + help="Address offset in flash that file was read from.", + required=True, + type=esptool.arg_auto_int, + ) + p.add_argument( + "--flash_crypt_conf", + help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", + required=False, + default=0xF, + type=esptool.arg_auto_int, + ) - p = subparsers.add_parser('encrypt_flash_data', help='Encrypt some data suitable for encrypted flash (using known key)') - p.add_argument('--aes_xts', '-x', help="Encrypt data using AES-XTS as used on ESP32-S2, ESP32-C2 and ESP32-C3", action='store_true') - p.add_argument('--keyfile', '-k', help="File with flash encryption key", type=argparse.FileType('rb'), - required=True) - p.add_argument('--output', '-o', help="Output file for encrypted data.", type=OutFileType(), - required=True) - p.add_argument('--address', '-a', help="Address offset in flash where file will be flashed.", required=True, type=esptool.arg_auto_int) - p.add_argument('--flash_crypt_conf', help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", required=False, default=0xF, type=esptool.arg_auto_int) - p.add_argument('plaintext_file', help="File with plaintext content for encrypting", type=argparse.FileType('rb')) + p = subparsers.add_parser( + "encrypt_flash_data", + help="Encrypt some data suitable for encrypted flash (using known key)", + ) + p.add_argument( + "--aes_xts", + "-x", + help="Encrypt data using AES-XTS as used on ESP32-S2, ESP32-C2 and ESP32-C3", + action="store_true", + ) + p.add_argument( + "--keyfile", + "-k", + help="File with flash encryption key", + type=argparse.FileType("rb"), + required=True, + ) + p.add_argument( + "--output", + "-o", + help="Output file for encrypted data.", + type=OutFileType(), + required=True, + ) + p.add_argument( + "--address", + "-a", + help="Address offset in flash where file will be flashed.", + required=True, + type=esptool.arg_auto_int, + ) + p.add_argument( + "--flash_crypt_conf", + help="Override FLASH_CRYPT_CONF efuse value (default is 0XF).", + required=False, + default=0xF, + type=esptool.arg_auto_int, + ) + p.add_argument( + "plaintext_file", + help="File with plaintext content for encrypting", + type=argparse.FileType("rb"), + ) args = parser.parse_args(custom_commandline) - print('espsecure.py v%s' % esptool.__version__) + print("espsecure.py v%s" % esptool.__version__) if args.operation is None: parser.print_help() parser.exit(1) @@ -1137,9 +1588,9 @@ def _main(): try: main() except esptool.FatalError as e: - print('\nA fatal error occurred: %s' % e) + print("\nA fatal error occurred: %s" % e) sys.exit(2) -if __name__ == '__main__': +if __name__ == "__main__": _main() diff --git a/esptool.py b/esptool.py index a974194..9f5d62e 100755 --- a/esptool.py +++ b/esptool.py @@ -1,15 +1,17 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -# This executable script is a thin wrapper around the main functionality in the esptool Python package +# This executable script is a thin wrapper around the main functionality +# in the esptool Python package # -# If esptool is installed via setup.py or pip then this file is not used at all, it's compatibility -# for the older "run from source dir" esptool approach. +# If esptool is installed via setup.py or pip then this file is not used at all, +# it's compatibility for the older "run from source dir" esptool approach. import esptool -if __name__ == '__main__': +if __name__ == "__main__": esptool._main() diff --git a/esptool/__init__.py b/esptool/__init__.py index 397ad5c..4e9dc2d 100644 --- a/esptool/__init__.py +++ b/esptool/__init__.py @@ -1,12 +1,34 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later -__all__ = ['chip_id', 'detect_chip', 'dump_mem', 'elf2image', 'erase_flash', 'erase_region', 'flash_id', 'get_security_info', - 'image_info', 'load_ram', 'make_image', 'merge_bin', 'read_flash', 'read_flash_status', 'read_mac', - 'read_mem', 'run', 'verify_flash', 'version', 'write_flash', 'write_flash_status', 'write_mem'] +__all__ = [ + "chip_id", + "detect_chip", + "dump_mem", + "elf2image", + "erase_flash", + "erase_region", + "flash_id", + "get_security_info", + "image_info", + "load_ram", + "make_image", + "merge_bin", + "read_flash", + "read_flash_status", + "read_mac", + "read_mem", + "run", + "verify_flash", + "version", + "write_flash", + "write_flash_status", + "write_mem", +] __version__ = "4.0-dev" @@ -44,112 +66,155 @@ from esptool.cmds import ( ) from esptool.loader import DEFAULT_CONNECT_ATTEMPTS, ESPLoader, list_ports from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM, ESP8266ROM -from esptool.util import FatalError, NotImplementedInROMError, PYTHON2, flash_size_bytes, format_chip_name +from esptool.util import ( + FatalError, + NotImplementedInROMError, + PYTHON2, + flash_size_bytes, + format_chip_name, +) def main(argv=None, esp=None): """ Main function for esptool - argv - 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. "-b 115200" thus - becomes ['-b', '115200']. + argv - 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. "-b 115200" thus becomes ['-b', '115200']. - esp - Optional override of the connected device previously returned by get_default_connected_device() + esp - Optional override of the connected device previously + returned by get_default_connected_device() """ external_esp = esp is not None - parser = argparse.ArgumentParser(description='esptool.py v%s - Espressif chips ROM Bootloader Utility' % __version__, prog='esptool') - - parser.add_argument('--chip', '-c', - help='Target chip type', - type=format_chip_name, # support ESP32-S2, etc. - choices=['auto'] + CHIP_LIST, - default=os.environ.get('ESPTOOL_CHIP', 'auto')) + parser = argparse.ArgumentParser( + description="esptool.py v%s - Espressif chips ROM Bootloader Utility" + % __version__, + prog="esptool", + ) parser.add_argument( - '--port', '-p', - help='Serial port device', - default=os.environ.get('ESPTOOL_PORT', None)) + "--chip", + "-c", + help="Target chip type", + type=format_chip_name, # support ESP32-S2, etc. + choices=["auto"] + CHIP_LIST, + default=os.environ.get("ESPTOOL_CHIP", "auto"), + ) parser.add_argument( - '--baud', '-b', - help='Serial port baud rate used when flashing/reading', + "--port", + "-p", + help="Serial port device", + default=os.environ.get("ESPTOOL_PORT", None), + ) + + parser.add_argument( + "--baud", + "-b", + help="Serial port baud rate used when flashing/reading", type=arg_auto_int, - default=os.environ.get('ESPTOOL_BAUD', ESPLoader.ESP_ROM_BAUD)) + default=os.environ.get("ESPTOOL_BAUD", ESPLoader.ESP_ROM_BAUD), + ) 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=os.environ.get('ESPTOOL_BEFORE', 'default_reset')) + "--before", + help="What to do before connecting to the chip", + choices=["default_reset", "usb_reset", "no_reset", "no_reset_no_sync"], + default=os.environ.get("ESPTOOL_BEFORE", "default_reset"), + ) parser.add_argument( - '--after', '-a', - help='What to do after esptool.py is finished', - choices=['hard_reset', 'soft_reset', 'no_reset', 'no_reset_stub'], - default=os.environ.get('ESPTOOL_AFTER', 'hard_reset')) + "--after", + "-a", + help="What to do after esptool.py is finished", + choices=["hard_reset", "soft_reset", "no_reset", "no_reset_stub"], + default=os.environ.get("ESPTOOL_AFTER", "hard_reset"), + ) parser.add_argument( - '--no-stub', - help="Disable launching the flasher stub, only talk to ROM bootloader. Some features will not be available.", - action='store_true') + "--no-stub", + help="Disable launching the flasher stub, only talk to ROM bootloader. " + "Some features will not be available.", + action="store_true", + ) parser.add_argument( - '--trace', '-t', + "--trace", + "-t", help="Enable trace-level output of esptool.py interactions.", - action='store_true') + action="store_true", + ) parser.add_argument( - '--override-vddsdio', + "--override-vddsdio", help="Override ESP32 VDDSDIO internal voltage regulator (use with care)", choices=ESP32ROM.OVERRIDE_VDDSDIO_CHOICES, - nargs='?') + nargs="?", + ) parser.add_argument( - '--connect-attempts', - help=('Number of attempts to connect, negative or 0 for infinite. ' - 'Default: %d.' % DEFAULT_CONNECT_ATTEMPTS), + "--connect-attempts", + help=( + "Number of attempts to connect, negative or 0 for infinite. " + "Default: %d." % DEFAULT_CONNECT_ATTEMPTS + ), type=int, - default=os.environ.get('ESPTOOL_CONNECT_ATTEMPTS', DEFAULT_CONNECT_ATTEMPTS)) + default=os.environ.get("ESPTOOL_CONNECT_ATTEMPTS", DEFAULT_CONNECT_ATTEMPTS), + ) subparsers = parser.add_subparsers( - dest='operation', - help='Run esptool {command} -h for additional help') + dest="operation", help="Run esptool {command} -h for additional help" + ) def add_spi_connection_arg(parent): - parent.add_argument('--spi-connection', '-sc', help='ESP32-only argument. Override default SPI Flash connection. ' - 'Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers to use for SPI flash (CLK,Q,D,HD,CS).', - action=SpiConnectionAction) + parent.add_argument( + "--spi-connection", + "-sc", + help="ESP32-only argument. Override default SPI Flash connection. " + "Value can be SPI, HSPI or a comma-separated list of 5 I/O numbers " + "to use for SPI flash (CLK,Q,D,HD,CS).", + action=SpiConnectionAction, + ) parser_load_ram = subparsers.add_parser( - 'load_ram', - help='Download an image to RAM and execute') - parser_load_ram.add_argument('filename', help='Firmware image') + "load_ram", help="Download an image to RAM and execute" + ) + parser_load_ram.add_argument("filename", help="Firmware image") parser_dump_mem = subparsers.add_parser( - 'dump_mem', - help='Dump arbitrary memory to disk') - parser_dump_mem.add_argument('address', help='Base address', type=arg_auto_int) - parser_dump_mem.add_argument('size', help='Size of region to dump', type=arg_auto_int) - parser_dump_mem.add_argument('filename', help='Name of binary dump') + "dump_mem", help="Dump arbitrary memory to disk" + ) + parser_dump_mem.add_argument("address", help="Base address", type=arg_auto_int) + parser_dump_mem.add_argument( + "size", help="Size of region to dump", type=arg_auto_int + ) + parser_dump_mem.add_argument("filename", help="Name of binary dump") parser_read_mem = subparsers.add_parser( - 'read_mem', - help='Read arbitrary memory location') - parser_read_mem.add_argument('address', help='Address to read', type=arg_auto_int) + "read_mem", help="Read arbitrary memory location" + ) + parser_read_mem.add_argument("address", help="Address to read", type=arg_auto_int) parser_write_mem = subparsers.add_parser( - 'write_mem', - help='Read-modify-write to arbitrary memory location') - parser_write_mem.add_argument('address', help='Address to write', type=arg_auto_int) - parser_write_mem.add_argument('value', help='Value', type=arg_auto_int) - parser_write_mem.add_argument('mask', help='Mask of bits to write', type=arg_auto_int, nargs='?', default='0xFFFFFFFF') + "write_mem", help="Read-modify-write to arbitrary memory location" + ) + parser_write_mem.add_argument("address", help="Address to write", type=arg_auto_int) + parser_write_mem.add_argument("value", help="Value", type=arg_auto_int) + parser_write_mem.add_argument( + "mask", + help="Mask of bits to write", + type=arg_auto_int, + nargs="?", + default="0xFFFFFFFF", + ) def add_spi_flash_subparsers(parent, allow_keep, auto_detect): - """ Add common parser arguments for SPI flash properties """ - extra_keep_args = ['keep'] if allow_keep else [] + """Add common parser arguments for SPI flash properties""" + extra_keep_args = ["keep"] if allow_keep else [] if auto_detect and allow_keep: extra_fs_message = ", detect, or keep" @@ -160,162 +225,317 @@ def main(argv=None, esp=None): else: extra_fs_message = "" - parent.add_argument('--flash_freq', '-ff', help='SPI Flash frequency', - choices=extra_keep_args + ['80m', '60m', '48m', '40m', '30m', '26m', '24m', '20m', '16m', '15m', '12m'], - default=os.environ.get('ESPTOOL_FF', 'keep' if allow_keep else '40m')) - parent.add_argument('--flash_mode', '-fm', help='SPI Flash mode', - choices=extra_keep_args + ['qio', 'qout', 'dio', 'dout'], - default=os.environ.get('ESPTOOL_FM', 'keep' if allow_keep else 'qio')) - parent.add_argument('--flash_size', '-fs', help='SPI Flash size in MegaBytes (1MB, 2MB, 4MB, 8MB, 16MB, 32MB, 64MB, 128MB)' - ' plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)' + extra_fs_message, - action=FlashSizeAction, auto_detect=auto_detect, - default=os.environ.get('ESPTOOL_FS', 'keep' if allow_keep else '1MB')) + parent.add_argument( + "--flash_freq", + "-ff", + help="SPI Flash frequency", + choices=extra_keep_args + + [ + "80m", + "60m", + "48m", + "40m", + "30m", + "26m", + "24m", + "20m", + "16m", + "15m", + "12m", + ], + default=os.environ.get("ESPTOOL_FF", "keep" if allow_keep else "40m"), + ) + parent.add_argument( + "--flash_mode", + "-fm", + help="SPI Flash mode", + choices=extra_keep_args + ["qio", "qout", "dio", "dout"], + default=os.environ.get("ESPTOOL_FM", "keep" if allow_keep else "qio"), + ) + parent.add_argument( + "--flash_size", + "-fs", + help="SPI Flash size in MegaBytes " + "(1MB, 2MB, 4MB, 8MB, 16MB, 32MB, 64MB, 128MB) " + "plus ESP8266-only (256KB, 512KB, 2MB-c1, 4MB-c1)" + extra_fs_message, + action=FlashSizeAction, + auto_detect=auto_detect, + default=os.environ.get("ESPTOOL_FS", "keep" if allow_keep else "1MB"), + ) add_spi_connection_arg(parent) parser_write_flash = subparsers.add_parser( - 'write_flash', - help='Write a binary blob to flash') + "write_flash", help="Write a binary blob to flash" + ) - parser_write_flash.add_argument('addr_filename', metavar='
', help='Address followed by binary filename, separated by space', - action=AddrFilenamePairAction) - parser_write_flash.add_argument('--erase-all', '-e', - help='Erase all regions of flash (not just write areas) before programming', - action="store_true") + parser_write_flash.add_argument( + "addr_filename", + metavar="
", + help="Address followed by binary filename, separated by space", + action=AddrFilenamePairAction, + ) + parser_write_flash.add_argument( + "--erase-all", + "-e", + help="Erase all regions of flash (not just write areas) before programming", + action="store_true", + ) add_spi_flash_subparsers(parser_write_flash, allow_keep=True, auto_detect=True) - parser_write_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") - parser_write_flash.add_argument('--verify', help='Verify just-written data on flash ' - '(mostly superfluous, data is read back during flashing)', action='store_true') - parser_write_flash.add_argument('--encrypt', help='Apply flash encryption when writing data (required correct efuse settings)', - action='store_true') - # In order to not break backward compatibility, our list of encrypted files to flash is a new parameter - parser_write_flash.add_argument('--encrypt-files', metavar='
', - help='Files to be encrypted on the flash. Address followed by binary filename, separated by space.', - action=AddrFilenamePairAction) - parser_write_flash.add_argument('--ignore-flash-encryption-efuse-setting', help='Ignore flash encryption efuse settings ', - action='store_true') + parser_write_flash.add_argument( + "--no-progress", "-p", help="Suppress progress output", action="store_true" + ) + parser_write_flash.add_argument( + "--verify", + help="Verify just-written data on flash " + "(mostly superfluous, data is read back during flashing)", + action="store_true", + ) + parser_write_flash.add_argument( + "--encrypt", + help="Apply flash encryption when writing data " + "(required correct efuse settings)", + action="store_true", + ) + # In order to not break backward compatibility, + # our list of encrypted files to flash is a new parameter + parser_write_flash.add_argument( + "--encrypt-files", + metavar="
", + help="Files to be encrypted on the flash. " + "Address followed by binary filename, separated by space.", + action=AddrFilenamePairAction, + ) + parser_write_flash.add_argument( + "--ignore-flash-encryption-efuse-setting", + help="Ignore flash encryption efuse settings ", + action="store_true", + ) compress_args = parser_write_flash.add_mutually_exclusive_group(required=False) - compress_args.add_argument('--compress', '-z', help='Compress data in transfer (default unless --no-stub is specified)', - action="store_true", default=None) - compress_args.add_argument('--no-compress', '-u', help='Disable data compression during transfer (default if --no-stub is specified)', - action="store_true") + compress_args.add_argument( + "--compress", + "-z", + help="Compress data in transfer (default unless --no-stub is specified)", + action="store_true", + default=None, + ) + compress_args.add_argument( + "--no-compress", + "-u", + help="Disable data compression during transfer " + "(default if --no-stub is specified)", + action="store_true", + ) - subparsers.add_parser( - 'run', - help='Run application code in flash') + subparsers.add_parser("run", help="Run application code in flash") parser_image_info = subparsers.add_parser( - 'image_info', - help='Dump headers from an application image') - parser_image_info.add_argument('filename', help='Image file to parse') + "image_info", help="Dump headers from an application image" + ) + parser_image_info.add_argument("filename", help="Image file to parse") parser_make_image = subparsers.add_parser( - 'make_image', - help='Create an application image from binary files') - parser_make_image.add_argument('output', help='Output image file') - parser_make_image.add_argument('--segfile', '-f', action='append', help='Segment input file') - parser_make_image.add_argument('--segaddr', '-a', action='append', help='Segment base address', type=arg_auto_int) - parser_make_image.add_argument('--entrypoint', '-e', help='Address of entry point', type=arg_auto_int, default=0) + "make_image", help="Create an application image from binary files" + ) + parser_make_image.add_argument("output", help="Output image file") + parser_make_image.add_argument( + "--segfile", "-f", action="append", help="Segment input file" + ) + parser_make_image.add_argument( + "--segaddr", + "-a", + action="append", + help="Segment base address", + type=arg_auto_int, + ) + parser_make_image.add_argument( + "--entrypoint", + "-e", + help="Address of entry point", + type=arg_auto_int, + default=0, + ) parser_elf2image = subparsers.add_parser( - 'elf2image', - help='Create an application image from ELF file') - parser_elf2image.add_argument('input', help='Input ELF file') - parser_elf2image.add_argument('--output', '-o', help='Output filename prefix (for version 1 image), or filename (for version 2 single image)', type=str) - parser_elf2image.add_argument('--version', '-e', help='Output image version', choices=['1', '2', '3'], default='1') - parser_elf2image.add_argument('--min-rev', '-r', help='Minimum chip revision', choices=['0', '1', '2', '3'], default='0') - parser_elf2image.add_argument('--secure-pad', action='store_true', - help='Pad image so once signed it will end on a 64KB boundary. For Secure Boot v1 images only.') - parser_elf2image.add_argument('--secure-pad-v2', action='store_true', - help='Pad image to 64KB, so once signed its signature sector will start at the next 64K block. ' - 'For Secure Boot v2 images only.') - parser_elf2image.add_argument('--elf-sha256-offset', help='If set, insert SHA256 hash (32 bytes) of the input ELF file at specified offset in the binary.', - type=arg_auto_int, default=None) - parser_elf2image.add_argument('--use_segments', help='If set, ELF segments will be used instead of ELF sections to genereate the image.', - action='store_true') - parser_elf2image.add_argument('--flash-mmu-page-size', help="Change flash MMU page size.", choices=['64KB', '32KB', '16KB']) + "elf2image", help="Create an application image from ELF file" + ) + parser_elf2image.add_argument("input", help="Input ELF file") + parser_elf2image.add_argument( + "--output", + "-o", + help="Output filename prefix (for version 1 image), " + "or filename (for version 2 single image)", + type=str, + ) + parser_elf2image.add_argument( + "--version", + "-e", + help="Output image version", + choices=["1", "2", "3"], + default="1", + ) + parser_elf2image.add_argument( + "--min-rev", + "-r", + help="Minimum chip revision", + choices=["0", "1", "2", "3"], + default="0", + ) + parser_elf2image.add_argument( + "--secure-pad", + action="store_true", + help="Pad image so once signed it will end on a 64KB boundary. " + "For Secure Boot v1 images only.", + ) + parser_elf2image.add_argument( + "--secure-pad-v2", + action="store_true", + help="Pad image to 64KB, so once signed its signature sector will" + "start at the next 64K block. For Secure Boot v2 images only.", + ) + parser_elf2image.add_argument( + "--elf-sha256-offset", + help="If set, insert SHA256 hash (32 bytes) of the input ELF file " + "at specified offset in the binary.", + type=arg_auto_int, + default=None, + ) + parser_elf2image.add_argument( + "--use_segments", + help="If set, ELF segments will be used instead of ELF sections " + "to genereate the image.", + action="store_true", + ) + parser_elf2image.add_argument( + "--flash-mmu-page-size", + help="Change flash MMU page size.", + choices=["64KB", "32KB", "16KB"], + ) add_spi_flash_subparsers(parser_elf2image, allow_keep=False, auto_detect=False) - subparsers.add_parser( - 'read_mac', - help='Read MAC address from OTP ROM') + subparsers.add_parser("read_mac", help="Read MAC address from OTP ROM") - subparsers.add_parser( - 'chip_id', - help='Read Chip ID from OTP ROM') + subparsers.add_parser("chip_id", help="Read Chip ID from OTP ROM") parser_flash_id = subparsers.add_parser( - 'flash_id', - help='Read SPI flash manufacturer and device ID') + "flash_id", help="Read SPI flash manufacturer and device ID" + ) add_spi_connection_arg(parser_flash_id) parser_read_status = subparsers.add_parser( - 'read_flash_status', - help='Read SPI flash status register') + "read_flash_status", help="Read SPI flash status register" + ) add_spi_connection_arg(parser_read_status) - parser_read_status.add_argument('--bytes', help='Number of bytes to read (1-3)', type=int, choices=[1, 2, 3], default=2) + parser_read_status.add_argument( + "--bytes", + help="Number of bytes to read (1-3)", + type=int, + choices=[1, 2, 3], + default=2, + ) parser_write_status = subparsers.add_parser( - 'write_flash_status', - help='Write SPI flash status register') + "write_flash_status", help="Write SPI flash status register" + ) add_spi_connection_arg(parser_write_status) - parser_write_status.add_argument('--non-volatile', help='Write non-volatile bits (use with caution)', action='store_true') - parser_write_status.add_argument('--bytes', help='Number of status bytes to write (1-3)', type=int, choices=[1, 2, 3], default=2) - parser_write_status.add_argument('value', help='New value', type=arg_auto_int) + parser_write_status.add_argument( + "--non-volatile", + help="Write non-volatile bits (use with caution)", + action="store_true", + ) + parser_write_status.add_argument( + "--bytes", + help="Number of status bytes to write (1-3)", + type=int, + choices=[1, 2, 3], + default=2, + ) + parser_write_status.add_argument("value", help="New value", type=arg_auto_int) parser_read_flash = subparsers.add_parser( - 'read_flash', - help='Read SPI flash content') + "read_flash", help="Read SPI flash content" + ) add_spi_connection_arg(parser_read_flash) - parser_read_flash.add_argument('address', help='Start address', type=arg_auto_int) - parser_read_flash.add_argument('size', help='Size of region to dump', type=arg_auto_int) - parser_read_flash.add_argument('filename', help='Name of binary dump') - parser_read_flash.add_argument('--no-progress', '-p', help='Suppress progress output', action="store_true") + parser_read_flash.add_argument("address", help="Start address", type=arg_auto_int) + parser_read_flash.add_argument( + "size", help="Size of region to dump", type=arg_auto_int + ) + parser_read_flash.add_argument("filename", help="Name of binary dump") + parser_read_flash.add_argument( + "--no-progress", "-p", help="Suppress progress output", action="store_true" + ) parser_verify_flash = subparsers.add_parser( - 'verify_flash', - help='Verify a binary blob against flash') - parser_verify_flash.add_argument('addr_filename', help='Address and binary file to verify there, separated by space', - action=AddrFilenamePairAction) - parser_verify_flash.add_argument('--diff', '-d', help='Show differences', - choices=['no', 'yes'], default='no') + "verify_flash", help="Verify a binary blob against flash" + ) + parser_verify_flash.add_argument( + "addr_filename", + help="Address and binary file to verify there, separated by space", + action=AddrFilenamePairAction, + ) + parser_verify_flash.add_argument( + "--diff", "-d", help="Show differences", choices=["no", "yes"], default="no" + ) add_spi_flash_subparsers(parser_verify_flash, allow_keep=True, auto_detect=True) parser_erase_flash = subparsers.add_parser( - 'erase_flash', - help='Perform Chip Erase on SPI flash') + "erase_flash", help="Perform Chip Erase on SPI flash" + ) add_spi_connection_arg(parser_erase_flash) parser_erase_region = subparsers.add_parser( - 'erase_region', - help='Erase a region of the flash') + "erase_region", help="Erase a region of the flash" + ) add_spi_connection_arg(parser_erase_region) - parser_erase_region.add_argument('address', help='Start address (must be multiple of 4096)', type=arg_auto_int) - parser_erase_region.add_argument('size', help='Size of region to erase (must be multiple of 4096)', type=arg_auto_int) + parser_erase_region.add_argument( + "address", help="Start address (must be multiple of 4096)", type=arg_auto_int + ) + parser_erase_region.add_argument( + "size", + help="Size of region to erase (must be multiple of 4096)", + type=arg_auto_int, + ) parser_merge_bin = subparsers.add_parser( - 'merge_bin', - help='Merge multiple raw binary files into a single file for later flashing') + "merge_bin", + help="Merge multiple raw binary files into a single file for later flashing", + ) - parser_merge_bin.add_argument('--output', '-o', help='Output filename', type=str, required=True) - parser_merge_bin.add_argument('--format', '-f', help='Format of the output file', choices='raw', default='raw') # for future expansion + parser_merge_bin.add_argument( + "--output", "-o", help="Output filename", type=str, required=True + ) + parser_merge_bin.add_argument( + "--format", "-f", help="Format of the output file", choices="raw", default="raw" + ) # for future expansion add_spi_flash_subparsers(parser_merge_bin, allow_keep=True, auto_detect=False) - parser_merge_bin.add_argument('--target-offset', '-t', help='Target offset where the output file will be flashed', - type=arg_auto_int, default=0) - parser_merge_bin.add_argument('--fill-flash-size', help='If set, the final binary file will be padded with FF ' - 'bytes up to this flash size.', action=FlashSizeAction) - parser_merge_bin.add_argument('addr_filename', metavar='
', - help='Address followed by binary filename, separated by space', - action=AddrFilenamePairAction) + parser_merge_bin.add_argument( + "--target-offset", + "-t", + help="Target offset where the output file will be flashed", + type=arg_auto_int, + default=0, + ) + parser_merge_bin.add_argument( + "--fill-flash-size", + help="If set, the final binary file will be padded with FF " + "bytes up to this flash size.", + action=FlashSizeAction, + ) + parser_merge_bin.add_argument( + "addr_filename", + metavar="
", + help="Address followed by binary filename, separated by space", + action=AddrFilenamePairAction, + ) - subparsers.add_parser('get_security_info', help='Get some security-related data') + subparsers.add_parser("get_security_info", help="Get some security-related data") - subparsers.add_parser('version', help='Print esptool version') + subparsers.add_parser("version", help="Print esptool version") # internal sanity check - every operation matches a module function of the same name for operation in subparsers.choices.keys(): @@ -324,7 +544,7 @@ def main(argv=None, esp=None): argv = expand_file_arguments(argv or sys.argv[1:]) args = parser.parse_args(argv) - print('esptool.py v%s' % __version__) + print("esptool.py v%s" % __version__) # operation function can take 1 arg (args), 2 args (esp, arg) # or be a member function of the ESPLoader class. @@ -337,8 +557,15 @@ def main(argv=None, esp=None): # and --encrypt-files, which represents the list of files to encrypt. # The reason is that allowing both at the same time increases the chances of # having contradictory lists (e.g. one file not available in one of list). - if args.operation == "write_flash" and args.encrypt and args.encrypt_files is not None: - raise FatalError("Options --encrypt and --encrypt-files must not be specified at the same time.") + if ( + args.operation == "write_flash" + and args.encrypt + and args.encrypt_files is not None + ): + raise FatalError( + "Options --encrypt and --encrypt-files " + "must not be specified at the same time." + ) operation_func = globals()[args.operation] @@ -348,9 +575,13 @@ def main(argv=None, esp=None): else: operation_args = inspect.getfullargspec(operation_func).args - if operation_args[0] == 'esp': # operation function takes an ESPLoader connection object + if ( + operation_args[0] == "esp" + ): # operation function takes an ESPLoader connection object if args.before != "no_reset_no_sync": - initial_baud = min(ESPLoader.ESP_ROM_BAUD, args.baud) # don't sync faster than the default baud rate + initial_baud = min( + ESPLoader.ESP_ROM_BAUD, args.baud + ) # don't sync faster than the default baud rate else: initial_baud = args.baud @@ -359,12 +590,21 @@ def main(argv=None, esp=None): print("Found %d serial ports" % len(ser_list)) else: ser_list = [args.port] - esp = esp or get_default_connected_device(ser_list, port=args.port, connect_attempts=args.connect_attempts, - initial_baud=initial_baud, chip=args.chip, trace=args.trace, - before=args.before) + esp = esp or get_default_connected_device( + ser_list, + port=args.port, + connect_attempts=args.connect_attempts, + initial_baud=initial_baud, + chip=args.chip, + trace=args.trace, + before=args.before, + ) if esp is None: - raise FatalError("Could not connect to an Espressif device on any of the %d available serial ports." % len(ser_list)) + raise FatalError( + "Could not connect to an Espressif device " + "on any of the %d available serial ports." % len(ser_list) + ) if esp.secure_download_mode: print("Chip is %s in Secure Download Mode" % esp.CHIP_NAME) @@ -376,10 +616,16 @@ def main(argv=None, esp=None): if not args.no_stub: if esp.secure_download_mode: - print("WARNING: Stub loader is not supported in Secure Download Mode, setting --no-stub") + print( + "WARNING: Stub loader is not supported in Secure Download Mode, " + "setting --no-stub" + ) args.no_stub = True elif not esp.IS_STUB and esp.stub_is_disabled: - print("WARNING: Stub loader has been disabled for compatibility, setting --no-stub") + print( + "WARNING: Stub loader has been disabled for compatibility, " + "setting --no-stub" + ) args.no_stub = True else: esp = esp.run_stub() @@ -391,12 +637,17 @@ def main(argv=None, esp=None): try: esp.change_baud(args.baud) except NotImplementedInROMError: - print("WARNING: ROM doesn't support changing baud rate. Keeping initial baud rate %d" % initial_baud) + print( + "WARNING: ROM doesn't support changing baud rate. " + "Keeping initial baud rate %d" % initial_baud + ) # override common SPI flash parameter stuff if configured to do so if hasattr(args, "spi_connection") and args.spi_connection is not None: if esp.CHIP_NAME != "ESP32": - raise FatalError("Chip %s does not support --spi-connection option." % esp.CHIP_NAME) + raise FatalError( + "Chip %s does not support --spi-connection option." % esp.CHIP_NAME + ) print("Configuring SPI flash mode...") esp.flash_spi_attach(args.spi_connection) elif args.no_stub: @@ -409,11 +660,11 @@ def main(argv=None, esp=None): def is_xmc_chip_strict(): id = esp.flash_id() - rdid = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00) + rdid = ((id & 0xFF) << 16) | ((id >> 16) & 0xFF) | (id & 0xFF00) - vendor_id = ((rdid >> 16) & 0xFF) - mfid = ((rdid >> 8) & 0xFF) - cpid = (rdid & 0xFF) + vendor_id = (rdid >> 16) & 0xFF + mfid = (rdid >> 8) & 0xFF + cpid = rdid & 0xFF if vendor_id != XMC_VENDOR_ID: return False @@ -441,11 +692,14 @@ def main(argv=None, esp=None): if mf_id != XMC_VENDOR_ID: # Non-XMC chip detected by SFDP Read, skipping. return - print("WARNING: XMC flash chip boot-up failure detected! Running XMC25QHxxC startup flow") + print( + "WARNING: XMC flash chip boot-up failure detected! " + "Running XMC25QHxxC startup flow" + ) esp.run_spiflash_command(0xB9) # Enter DPD esp.run_spiflash_command(0x79) # Enter UDPD esp.run_spiflash_command(0xFF) # Exit UDPD - time.sleep(0.002) # Delay tXUDPD + time.sleep(0.002) # Delay tXUDPD esp.run_spiflash_command(0xAB) # Release Power-Down time.sleep(0.00002) # Check for success @@ -457,31 +711,43 @@ def main(argv=None, esp=None): if not esp.secure_download_mode: try: flash_id = esp.flash_id() - if flash_id in (0xffffff, 0x000000): - print('WARNING: Failed to communicate with the flash chip, read/write operations will fail. ' - 'Try checking the chip connections or removing any other hardware connected to IOs.') + if flash_id in (0xFFFFFF, 0x000000): + print( + "WARNING: Failed to communicate with the flash chip, " + "read/write operations will fail. " + "Try checking the chip connections or removing " + "any other hardware connected to IOs." + ) except Exception as e: - esp.trace('Unable to verify flash chip connection ({}).'.format(e)) + esp.trace("Unable to verify flash chip connection ({}).".format(e)) # Check if XMC SPI flash chip booted-up successfully, fix if not if not esp.secure_download_mode: try: flash_xmc_startup() except Exception as e: - esp.trace('Unable to perform XMC flash chip startup sequence ({}).'.format(e)) + esp.trace( + "Unable to perform XMC flash chip startup sequence ({}).".format(e) + ) if hasattr(args, "flash_size"): print("Configuring flash size...") detect_flash_size(esp, args) - if args.flash_size != 'keep': # TODO: should set this even with 'keep' + if args.flash_size != "keep": # TODO: should set this even with 'keep' esp.flash_set_parameters(flash_size_bytes(args.flash_size)) # Check if stub supports chosen flash size - if esp.IS_STUB and args.flash_size in ('32MB', '64MB', '128MB'): - print("WARNING: Flasher stub doesn't fully support flash size larger than 16MB, in case of failure use --no-stub.") + if esp.IS_STUB and args.flash_size in ("32MB", "64MB", "128MB"): + print( + "WARNING: Flasher stub doesn't fully support flash size larger " + "than 16MB, in case of failure use --no-stub." + ) if esp.IS_STUB and hasattr(args, "address") and hasattr(args, "size"): if args.address + args.size > 0x1000000: - print("WARNING: Flasher stub doesn't fully support flash size larger than 16MB, in case of failure use --no-stub.") + print( + "WARNING: Flasher stub doesn't fully support flash size larger " + "than 16MB, in case of failure use --no-stub." + ) try: operation_func(esp, args) @@ -495,17 +761,17 @@ def main(argv=None, esp=None): # Handle post-operation behaviour (reset or other) if operation_func == load_ram: # the ESP is now running the loaded image, so let it run - print('Exiting immediately.') - elif args.after == 'hard_reset': + print("Exiting immediately.") + elif args.after == "hard_reset": esp.hard_reset() - elif args.after == 'soft_reset': - print('Soft resetting...') + elif args.after == "soft_reset": + print("Soft resetting...") # flash_finish will trigger a soft reset esp.soft_reset(False) - elif args.after == 'no_reset_stub': - print('Staying in flasher stub.') + elif args.after == "no_reset_stub": + print("Staying in flasher stub.") else: # args.after == 'no_reset' - print('Staying in bootloader.') + print("Staying in bootloader.") if esp.IS_STUB: esp.soft_reset(True) # exit stub back to ROM loader @@ -522,15 +788,20 @@ def arg_auto_int(x): def get_port_list(): if list_ports is None: - raise FatalError("Listing all serial ports is currently not available. Please try to specify the port when " - "running esptool.py or update the pyserial package to the latest version") + raise FatalError( + "Listing all serial ports is currently not available. " + "Please try to specify the port when running esptool.py or update " + "the pyserial package to the latest version" + ) return sorted(ports.device for ports in list_ports.comports()) def expand_file_arguments(argv): - """ Any argument starting with "@" gets replaced with all values read from a text file. + """ + Any argument starting with "@" gets replaced with all values read from a text file. Text file arguments can be split by newline or by space. - Values are added "as-is", as if they were specified in this order on the command line. + Values are added "as-is", as if they were specified in this order + on the command line. """ new_args = [] expanded = False @@ -548,14 +819,23 @@ def expand_file_arguments(argv): return argv -def get_default_connected_device(serial_list, port, connect_attempts, initial_baud, chip='auto', trace=False, - before='default_reset'): +def get_default_connected_device( + serial_list, + port, + connect_attempts, + initial_baud, + chip="auto", + trace=False, + before="default_reset", +): _esp = None for each_port in reversed(serial_list): print("Serial port %s" % each_port) try: - if chip == 'auto': - _esp = detect_chip(each_port, initial_baud, before, trace, connect_attempts) + if chip == "auto": + _esp = detect_chip( + each_port, initial_baud, before, trace, connect_attempts + ) else: chip_class = CHIP_DEFS[chip] _esp = chip_class(each_port, initial_baud, trace) @@ -570,10 +850,14 @@ def get_default_connected_device(serial_list, port, connect_attempts, initial_ba class FlashSizeAction(argparse.Action): - """ Custom flash size parser class to support backwards compatibility with megabit size arguments. - - (At next major relase, remove deprecated sizes and this can become a 'normal' choices= argument again.) """ + Custom flash size parser class to support backwards compatibility + with megabit size arguments. + + (At next major relase, remove deprecated sizes and this can become + a 'normal' choices= argument again.) - ESPTOOL-419 + """ + def __init__(self, option_strings, dest, nargs=1, auto_detect=False, **kwargs): super(FlashSizeAction, self).__init__(option_strings, dest, nargs, **kwargs) self._auto_detect = auto_detect @@ -581,15 +865,18 @@ class FlashSizeAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): try: value = { - '2m': '256KB', - '4m': '512KB', - '8m': '1MB', - '16m': '2MB', - '32m': '4MB', - '16m-c1': '2MB-c1', - '32m-c1': '4MB-c1', + "2m": "256KB", + "4m": "512KB", + "8m": "1MB", + "16m": "2MB", + "32m": "4MB", + "16m-c1": "2MB-c1", + "32m-c1": "4MB-c1", }[values[0]] - print("WARNING: Flash size arguments in megabits like '%s' are deprecated." % (values[0])) + print( + "WARNING: Flash size arguments in megabits like '%s' are deprecated." + % (values[0]) + ) print("Please use the equivalent size '%s'." % (value)) print("Megabit arguments may be removed in a future release.") except KeyError: @@ -598,16 +885,23 @@ class FlashSizeAction(argparse.Action): known_sizes = dict(ESP8266ROM.FLASH_SIZES) known_sizes.update(ESP32ROM.FLASH_SIZES) if self._auto_detect: - known_sizes['detect'] = 'detect' - known_sizes['keep'] = 'keep' + known_sizes["detect"] = "detect" + known_sizes["keep"] = "keep" if value not in known_sizes: - raise argparse.ArgumentError(self, '%s is not a known flash size. Known sizes: %s' % (value, ", ".join(known_sizes.keys()))) + raise argparse.ArgumentError( + self, + "%s is not a known flash size. Known sizes: %s" + % (value, ", ".join(known_sizes.keys())), + ) setattr(namespace, self.dest, value) class SpiConnectionAction(argparse.Action): - """ Custom action to parse 'spi connection' override. Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. """ + Custom action to parse 'spi connection' override. + Values are SPI, HSPI, or a sequence of 5 pin numbers separated by commas. + """ + def __call__(self, parser, namespace, value, option_string=None): if value.upper() == "SPI": value = 0 @@ -616,27 +910,45 @@ class SpiConnectionAction(argparse.Action): elif "," in value: values = value.split(",") if len(values) != 5: - raise argparse.ArgumentError(self, '%s is not a valid list of comma-separate pin numbers. Must be 5 numbers - CLK,Q,D,HD,CS.' % value) + raise argparse.ArgumentError( + self, + "%s is not a valid list of comma-separate pin numbers. " + "Must be 5 numbers - CLK,Q,D,HD,CS." % value, + ) try: values = tuple(int(v, 0) for v in values) except ValueError: - raise argparse.ArgumentError(self, '%s is not a valid argument. All pins must be numeric values' % values) + raise argparse.ArgumentError( + self, + "%s is not a valid argument. All pins must be numeric values" + % values, + ) if any([v for v in values if v > 33 or v < 0]): - raise argparse.ArgumentError(self, 'Pin numbers must be in the range 0-33.') - # encode the pin numbers as a 32-bit integer with packed 6-bit values, the same way ESP32 ROM takes them + raise argparse.ArgumentError( + self, "Pin numbers must be in the range 0-33." + ) + # encode the pin numbers as a 32-bit integer with packed 6-bit values, + # the same way ESP32 ROM takes them # TODO: make this less ESP32 ROM specific somehow... clk, q, d, hd, cs = values value = (hd << 24) | (cs << 18) | (d << 12) | (q << 6) | clk else: - raise argparse.ArgumentError(self, '%s is not a valid spi-connection value. ' - 'Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS).' % value) + raise argparse.ArgumentError( + self, + "%s is not a valid spi-connection value. " + "Values are SPI, HSPI, or a sequence of 5 pin numbers CLK,Q,D,HD,CS)." + % value, + ) setattr(namespace, self.dest, value) class AddrFilenamePairAction(argparse.Action): - """ Custom parser class for the address/filename pairs passed as arguments """ - def __init__(self, option_strings, dest, nargs='+', **kwargs): - super(AddrFilenamePairAction, self).__init__(option_strings, dest, nargs, **kwargs) + """Custom parser class for the address/filename pairs passed as arguments""" + + def __init__(self, option_strings, dest, nargs="+", **kwargs): + super(AddrFilenamePairAction, self).__init__( + option_strings, dest, nargs, **kwargs + ) def __call__(self, parser, namespace, values, option_string=None): # validate pair arguments @@ -645,13 +957,19 @@ class AddrFilenamePairAction(argparse.Action): try: address = int(values[i], 0) except ValueError: - raise argparse.ArgumentError(self, 'Address "%s" must be a number' % values[i]) + raise argparse.ArgumentError( + self, 'Address "%s" must be a number' % values[i] + ) try: - argfile = open(values[i + 1], 'rb') + argfile = open(values[i + 1], "rb") except IOError as e: raise argparse.ArgumentError(self, e) except IndexError: - raise argparse.ArgumentError(self, 'Must be pairs of an address and the binary filename to write there') + raise argparse.ArgumentError( + self, + "Must be pairs of an address " + "and the binary filename to write there", + ) pairs.append((address, argfile)) # Sort the addresses and check for overlapping @@ -661,9 +979,15 @@ class AddrFilenamePairAction(argparse.Action): size = argfile.tell() argfile.seek(0) sector_start = address & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) - sector_end = ((address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) & ~(ESPLoader.FLASH_SECTOR_SIZE - 1)) - 1 + sector_end = ( + (address + size + ESPLoader.FLASH_SECTOR_SIZE - 1) + & ~(ESPLoader.FLASH_SECTOR_SIZE - 1) + ) - 1 if sector_start < end: - message = 'Detected overlap at address: 0x%x for file: %s' % (address, argfile.name) + message = "Detected overlap at address: 0x%x for file: %s" % ( + address, + argfile.name, + ) raise argparse.ArgumentError(self, message) end = sector_end setattr(namespace, self.dest, pairs) @@ -673,9 +997,9 @@ def _main(): try: main() except FatalError as e: - print('\nA fatal error occurred: %s' % e) + print("\nA fatal error occurred: %s" % e) sys.exit(2) -if __name__ == '__main__': +if __name__ == "__main__": _main() diff --git a/esptool/__main__.py b/esptool/__main__.py index 8267ad9..e5b9358 100644 --- a/esptool/__main__.py +++ b/esptool/__main__.py @@ -1,10 +1,11 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later import esptool -if __name__ == '__main__': +if __name__ == "__main__": esptool._main() diff --git a/esptool/bin_image.py b/esptool/bin_image.py index 350327c..5a20d22 100644 --- a/esptool/bin_image.py +++ b/esptool/bin_image.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python -# -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -37,22 +36,28 @@ def align_file_position(f, size): class ESPBOOTLOADER(object): - """These are constants related to software ESP8266 bootloader, working with 'v2' image files""" + """ + These are constants related to software ESP8266 bootloader, + working with 'v2' image files + """ # First byte of the "v2" application image IMAGE_V2_MAGIC = 0xEA - # First 'segment' value in a "v2" application image, appears to be a constant version value? + # First 'segment' value in a "v2" application image, + # appears to be a constant version value? IMAGE_V2_SEGMENT = 4 def LoadFirmwareImage(chip, filename): - """Load a firmware image. Can be for any supported SoC. + """ + Load a firmware image. Can be for any supported SoC. - ESP8266 images will be examined to determine if they are original ROM firmware images (ESP8266ROMFirmwareImage) - or "v2" OTA bootloader images. + ESP8266 images will be examined to determine if they are original ROM firmware + images (ESP8266ROMFirmwareImage) or "v2" OTA bootloader images. - Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) or ESP8266V2FirmwareImage (v2). + Returns a BaseFirmwareImage subclass, either ESP8266ROMFirmwareImage (v1) + or ESP8266V2FirmwareImage (v2). """ chip = re.sub(r"[-()]", "", chip.lower()) with open(filename, "rb") as f: @@ -171,8 +176,8 @@ class BaseFirmwareImage(object): def verify(self): if len(self.segments) > 16: raise FatalError( - "Invalid segment count %d (max 16). Usually this indicates a linker script problem." - % len(self.segments) + "Invalid segment count %d (max 16). " + "Usually this indicates a linker script problem." % len(self.segments) ) def load_segment(self, f, is_irom_segment=False): @@ -196,15 +201,24 @@ class BaseFirmwareImage(object): print("WARNING: Suspicious segment 0x%x, length %d" % (offset, size)) def maybe_patch_segment_data(self, f, segment_data): - """If SHA256 digest of the ELF file needs to be inserted into this segment, do so. Returns segment data.""" + """ + If SHA256 digest of the ELF file needs to be inserted into this segment, do so. + Returns segment data. + """ segment_len = len(segment_data) file_pos = f.tell() # file_pos is position in the .bin file - if (self.elf_sha256_offset >= file_pos and self.elf_sha256_offset < file_pos + segment_len): + if ( + self.elf_sha256_offset >= file_pos + and self.elf_sha256_offset < file_pos + segment_len + ): # SHA256 digest needs to be patched into this binary segment, # calculate offset of the digest inside the binary segment. patch_offset = self.elf_sha256_offset - file_pos # Sanity checks - if (patch_offset < self.SEG_HEADER_LEN or patch_offset + self.SHA256_DIGEST_LEN > segment_len): + if ( + patch_offset < self.SEG_HEADER_LEN + or patch_offset + self.SHA256_DIGEST_LEN > segment_len + ): raise FatalError( "Cannot place SHA256 digest on segment boundary" "(elf_sha256_offset=%d, file_pos=%d, segment_size=%d)" @@ -212,17 +226,27 @@ class BaseFirmwareImage(object): ) # offset relative to the data part patch_offset -= self.SEG_HEADER_LEN - if (segment_data[patch_offset: patch_offset + self.SHA256_DIGEST_LEN] != b"\x00" * self.SHA256_DIGEST_LEN): + if ( + segment_data[patch_offset : patch_offset + self.SHA256_DIGEST_LEN] + != b"\x00" * self.SHA256_DIGEST_LEN + ): raise FatalError( - "Contents of segment at SHA256 digest offset 0x%x are not all zero. Refusing to overwrite." - % self.elf_sha256_offset + "Contents of segment at SHA256 digest offset 0x%x are not all zero." + " Refusing to overwrite." % self.elf_sha256_offset ) assert len(self.elf_sha256) == self.SHA256_DIGEST_LEN - segment_data = (segment_data[0:patch_offset] + self.elf_sha256 + segment_data[patch_offset + self.SHA256_DIGEST_LEN:]) + segment_data = ( + segment_data[0:patch_offset] + + self.elf_sha256 + + segment_data[patch_offset + self.SHA256_DIGEST_LEN :] + ) return segment_data def save_segment(self, f, segment, checksum=None): - """Save the next segment to the image file, return next checksum value if provided""" + """ + Save the next segment to the image file, + return next checksum value if provided + """ segment_data = self.maybe_patch_segment_data(f, segment.data) f.write(struct.pack(" 0: last_addr = flash_segments[0].addr for segment in flash_segments[1:]: if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: raise FatalError( - ( - "Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " - "Can't generate binary. Suggest changing linker script or ELF to merge sections." - ) + "Segment loaded at 0x%08x lands in same 64KB flash mapping " + "as segment loaded at 0x%08x. Can't generate binary. " + "Suggest changing linker script or ELF to merge sections." % (segment.addr, last_addr) ) last_addr = segment.addr def get_alignment_data_needed(segment): - # Actual alignment (in data bytes) required for a segment header: positioned so that - # after we write the next 8 byte header, file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN + # Actual alignment (in data bytes) required for a segment header: + # positioned so that after we write the next 8 byte header, + # file_offs % IROM_ALIGN == segment.addr % IROM_ALIGN # - # (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is aligned - # IROM_ALIGN+0x18 to account for the binary file header + # (this is because the segment's vaddr may not be IROM_ALIGNed, + # more likely is aligned IROM_ALIGN+0x18 + # to account for the binary file header align_past = (segment.addr % self.IROM_ALIGN) - self.SEG_HEADER_LEN pad_len = (self.IROM_ALIGN - (f.tell() % self.IROM_ALIGN)) + align_past if pad_len == 0 or pad_len == self.IROM_ALIGN: return 0 # already aligned - # subtract SEG_HEADER_LEN a second time, as the padding block has a header as well + # subtract SEG_HEADER_LEN a second time, + # as the padding block has a header as well pad_len -= self.SEG_HEADER_LEN if pad_len < 0: pad_len += self.IROM_ALIGN @@ -658,16 +696,22 @@ class ESP32FirmwareImage(BaseFirmwareImage): # This ensures all mapped flash content will be verified. if not self.append_digest: raise FatalError( - "secure_pad only applies if a SHA-256 digest is also appended to the image" + "secure_pad only applies if a SHA-256 digest " + "is also appended to the image" ) align_past = (f.tell() + self.SEG_HEADER_LEN) % self.IROM_ALIGN - # 16 byte aligned checksum (force the alignment to simplify calculations) + # 16 byte aligned checksum + # (force the alignment to simplify calculations) checksum_space = 16 if self.secure_pad == "1": - # after checksum: SHA-256 digest + (to be added by signing process) version, signature + 12 trailing bytes due to alignment + # after checksum: SHA-256 digest + + # (to be added by signing process) version, + # signature + 12 trailing bytes due to alignment space_after_checksum = 32 + 4 + 64 + 12 elif self.secure_pad == "2": # Secure Boot V2 - # after checksum: SHA-256 digest + signature sector, but we place signature sector after the 64KB boundary + # after checksum: SHA-256 digest + + # signature sector, + # but we place signature sector after the 64KB boundary space_after_checksum = 32 pad_len = ( self.IROM_ALIGN - align_past - checksum_space - space_after_checksum @@ -703,12 +747,15 @@ class ESP32FirmwareImage(BaseFirmwareImage): real_file.write(f.getvalue()) def save_flash_segment(self, f, segment, checksum=None): - """Save the next segment to the image file, return next checksum value if provided""" + """ + Save the next segment to the image file, return next checksum value if provided + """ segment_end_pos = f.tell() + len(segment.data) + self.SEG_HEADER_LEN segment_len_remainder = segment_end_pos % self.IROM_ALIGN if segment_len_remainder < 0x24: # Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the - # last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page boundary. + # last MMU page, if an IROM/DROM segment was < 0x24 bytes + # over the page boundary. segment.data += b"\x00" * (0x24 - segment_len_remainder) return self.save_segment(f, segment, checksum) @@ -740,7 +787,8 @@ class ESP32FirmwareImage(BaseFirmwareImage): # reserved fields in the middle should all be zero if any(f for f in fields[6:-1] if f != 0): print( - "Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?" + "Warning: some reserved header fields have non-zero values. " + "This image may be from a newer esptool.py?" ) append_digest = fields[-1] # last byte is append_digest @@ -788,7 +836,8 @@ class ESP8266V3FirmwareImage(ESP32FirmwareImage): checksum = ESPLoader.ESP_CHECKSUM_MAGIC - # split segments into flash-mapped vs ram-loaded, and take copies so we can mutate them + # split segments into flash-mapped vs ram-loaded, + # and take copies so we can mutate them flash_segments = [ copy.deepcopy(s) for s in sorted(self.segments, key=lambda s: s.addr) @@ -800,18 +849,17 @@ class ESP8266V3FirmwareImage(ESP32FirmwareImage): if not self.is_flash_addr(s.addr) and len(s.data) ] - # check for multiple ELF sections that are mapped in the same flash mapping region. - # this is usually a sign of a broken linker script, but if you have a legitimate - # use case then let us know + # check for multiple ELF sections that are mapped in the same + # flash mapping region. This is usually a sign of a broken linker script, + # but if you have a legitimate use case then let us know if len(flash_segments) > 0: last_addr = flash_segments[0].addr for segment in flash_segments[1:]: if segment.addr // self.IROM_ALIGN == last_addr // self.IROM_ALIGN: raise FatalError( - ( - "Segment loaded at 0x%08x lands in same 64KB flash mapping as segment loaded at 0x%08x. " - "Can't generate binary. Suggest changing linker script or ELF to merge sections." - ) + "Segment loaded at 0x%08x lands in same 64KB flash mapping " + "as segment loaded at 0x%08x. Can't generate binary. " + "Suggest changing linker script or ELF to merge sections." % (segment.addr, last_addr) ) last_addr = segment.addr @@ -881,7 +929,8 @@ class ESP8266V3FirmwareImage(ESP32FirmwareImage): # remaining fields in the middle should all be zero if any(f for f in fields[4:15] if f != 0): print( - "Warning: some reserved header fields have non-zero values. This image may be from a newer esptool.py?" + "Warning: some reserved header fields have non-zero values. " + "This image may be from a newer esptool.py?" ) @@ -1019,8 +1068,8 @@ class ELFFile(object): raise FatalError("%s has invalid ELF magic header" % self.name) if machine not in [0x5E, 0xF3]: raise FatalError( - "%s does not appear to be an Xtensa or an RISCV ELF file. e_machine=%04x" - % (self.name, machine) + "%s does not appear to be an Xtensa or an RISCV ELF file. " + "e_machine=%04x" % (self.name, machine) ) if shentsize != self.LEN_SEC_HEADER: raise FatalError( @@ -1043,8 +1092,8 @@ class ELFFile(object): ) if len(section_header) != (len_bytes): raise FatalError( - "Only read 0x%x bytes from section header (expected 0x%x.) Truncated ELF file?" - % (len(section_header), len_bytes) + "Only read 0x%x bytes from section header (expected 0x%x.) " + "Truncated ELF file?" % (len(section_header), len_bytes) ) # walk through the section header and extract all sections @@ -1072,8 +1121,9 @@ class ELFFile(object): f.seek(sec_offs) string_table = f.read(sec_size) - # build the real list of ELFSections by reading the actual section names from the - # string table section, and actual data for each section from the ELF file itself + # build the real list of ELFSections by reading the actual section names from + # the string table section, and actual data for each section + # from the ELF file itself def lookup_string(offs): raw = string_table[offs:] return raw[: raw.index(b"\x00")] @@ -1100,8 +1150,8 @@ class ELFFile(object): ) if len(segment_header) != (len_bytes): raise FatalError( - "Only read 0x%x bytes from segment header (expected 0x%x.) Truncated ELF file?" - % (len(segment_header), len_bytes) + "Only read 0x%x bytes from segment header (expected 0x%x.) " + "Truncated ELF file?" % (len(segment_header), len_bytes) ) # walk through the segment header and extract all segments diff --git a/esptool/cmds.py b/esptool/cmds.py index 10488af..bb201a6 100644 --- a/esptool/cmds.py +++ b/esptool/cmds.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python -# -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -29,27 +28,59 @@ from .bin_image import ( ESP8266V2FirmwareImage, ESP8266V3FirmwareImage, ) -from .loader import DEFAULT_CONNECT_ATTEMPTS, DEFAULT_TIMEOUT, ERASE_WRITE_TIMEOUT_PER_MB, ESPLoader, timeout_per_mb +from .loader import ( + DEFAULT_CONNECT_ATTEMPTS, + DEFAULT_TIMEOUT, + ERASE_WRITE_TIMEOUT_PER_MB, + ESPLoader, + timeout_per_mb, +) from .targets import CHIP_DEFS, CHIP_LIST, ROM_LIST -from .util import FatalError, NotImplementedInROMError, NotSupportedError, UnsupportedCommandError -from .util import PYTHON2, div_roundup, flash_size_bytes, hexify, pad_to, print_overwrite +from .util import ( + FatalError, + NotImplementedInROMError, + NotSupportedError, + UnsupportedCommandError, +) +from .util import ( + PYTHON2, + div_roundup, + flash_size_bytes, + hexify, + pad_to, + print_overwrite, +) -DETECTED_FLASH_SIZES = {0x12: '256KB', 0x13: '512KB', 0x14: '1MB', - 0x15: '2MB', 0x16: '4MB', 0x17: '8MB', - 0x18: '16MB', 0x19: '32MB', 0x1a: '64MB', 0x21: '128MB'} +DETECTED_FLASH_SIZES = { + 0x12: "256KB", + 0x13: "512KB", + 0x14: "1MB", + 0x15: "2MB", + 0x16: "4MB", + 0x17: "8MB", + 0x18: "16MB", + 0x19: "32MB", + 0x1A: "64MB", + 0x21: "128MB", +} -FLASH_MODES = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3} +FLASH_MODES = {"qio": 0, "qout": 1, "dio": 2, "dout": 3} -def detect_chip(port=ESPLoader.DEFAULT_PORT, baud=ESPLoader.ESP_ROM_BAUD, connect_mode='default_reset', trace_enabled=False, - connect_attempts=DEFAULT_CONNECT_ATTEMPTS): - """ Use serial access to detect the chip type. +def detect_chip( + port=ESPLoader.DEFAULT_PORT, + baud=ESPLoader.ESP_ROM_BAUD, + connect_mode="default_reset", + trace_enabled=False, + connect_attempts=DEFAULT_CONNECT_ATTEMPTS, +): + """Use serial access to detect the chip type. First, get_security_info command is sent to detect the ID of the chip (supported only by ESP32-C3 and later, works even in the Secure Download Mode). If this fails, we reconnect and fall-back to reading the magic number. It's mapped at a specific ROM address and has a different value on each chip model. - This way we can use one memory read and compare it to the magic number for each chip type. + This way we use one memory read and compare it to the magic number for each chip. This routine automatically performs ESPLoader.connect() (passing connect_mode parameter) as part of querying the chip. @@ -60,29 +91,42 @@ def detect_chip(port=ESPLoader.DEFAULT_PORT, baud=ESPLoader.ESP_ROM_BAUD, connec detect_port.USES_RFC2217 = True detect_port.connect(connect_mode, connect_attempts, detecting=True) try: - print('Detecting chip type...', end='') - res = detect_port.check_command('get security info', ESPLoader.ESP_GET_SECURITY_INFO, b'') - res = struct.unpack(", ) or a single # argument. + def load_ram(esp, args): image = LoadFirmwareImage(esp.CHIP_NAME, args.filename) - print('RAM boot...') + print("RAM boot...") for seg in image.segments: size = len(seg.data) - print('Downloading %d bytes at %08x...' % (size, seg.addr), end=' ') + print("Downloading %d bytes at %08x..." % (size, seg.addr), end=" ") sys.stdout.flush() - esp.mem_begin(size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr) + esp.mem_begin( + size, div_roundup(size, esp.ESP_RAM_BLOCK), esp.ESP_RAM_BLOCK, seg.addr + ) seq = 0 while len(seg.data) > 0: - esp.mem_block(seg.data[0:esp.ESP_RAM_BLOCK], seq) - seg.data = seg.data[esp.ESP_RAM_BLOCK:] + esp.mem_block(seg.data[0 : esp.ESP_RAM_BLOCK], seq) + seg.data = seg.data[esp.ESP_RAM_BLOCK :] seq += 1 - print('done!') + print("done!") - print('All segments done, executing at %08x' % image.entrypoint) + print("All segments done, executing at %08x" % image.entrypoint) esp.mem_finish(image.entrypoint) def read_mem(esp, args): - print('0x%08x = 0x%08x' % (args.address, esp.read_reg(args.address))) + print("0x%08x = 0x%08x" % (args.address, esp.read_reg(args.address))) def write_mem(esp, args): esp.write_reg(args.address, args.value, args.mask, 0) - print('Wrote %08x, mask %08x to %08x' % (args.value, args.mask, args.address)) + print("Wrote %08x, mask %08x to %08x" % (args.value, args.mask, args.address)) def dump_mem(esp, args): - with open(args.filename, 'wb') as f: + with open(args.filename, "wb") as f: for i in range(args.size // 4): d = esp.read_reg(args.address + (i * 4)) - f.write(struct.pack(b'> 16 args.flash_size = DETECTED_FLASH_SIZES.get(size_id) if args.flash_size is None: - print('Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x), defaulting to 4MB' % (flash_id, size_id)) - args.flash_size = '4MB' + print( + "Warning: Could not auto-detect Flash size (FlashID=0x%x, SizeID=0x%x)," + " defaulting to 4MB" % (flash_id, size_id) + ) + args.flash_size = "4MB" else: - print('Auto-detected Flash size:', args.flash_size) + print("Auto-detected Flash size:", args.flash_size) def _update_image_flash_params(esp, address, args, image): - """ Modify the flash mode & size bytes if this looks like an executable bootloader image """ + """ + Modify the flash mode & size bytes if this looks like an executable bootloader image + """ if len(image) < 8: return image # not long enough to be a bootloader image @@ -175,12 +237,15 @@ def _update_image_flash_params(esp, address, args, image): if address != esp.BOOTLOADER_FLASH_OFFSET: return image # not flashing bootloader offset, so don't modify this - if (args.flash_mode, args.flash_freq, args.flash_size) == ('keep',) * 3: + if (args.flash_mode, args.flash_freq, args.flash_size) == ("keep",) * 3: return image # all settings are 'keep', not modifying anything # easy check if this is an image: does it start with a magic byte? if magic != esp.ESP_IMAGE_MAGIC: - print("Warning: Image file at 0x%x doesn't look like an image file, so not changing any flash settings." % address) + print( + "Warning: Image file at 0x%x doesn't look like an image file, " + "so not changing any flash settings." % address + ) return image # make sure this really is an image, and not just data that @@ -190,23 +255,26 @@ def _update_image_flash_params(esp, address, args, image): test_image = esp.BOOTLOADER_IMAGE(io.BytesIO(image)) test_image.verify() except Exception: - print("Warning: Image file at 0x%x is not a valid %s image, so not changing any flash settings." % (address, esp.CHIP_NAME)) + print( + "Warning: Image file at 0x%x is not a valid %s image, " + "so not changing any flash settings." % (address, esp.CHIP_NAME) + ) return image - if args.flash_mode != 'keep': + if args.flash_mode != "keep": flash_mode = FLASH_MODES[args.flash_mode] flash_freq = flash_size_freq & 0x0F - if args.flash_freq != 'keep': + if args.flash_freq != "keep": flash_freq = esp.parse_flash_freq_arg(args.flash_freq) flash_size = flash_size_freq & 0xF0 - if args.flash_size != 'keep': + if args.flash_size != "keep": flash_size = esp.parse_flash_size_arg(args.flash_size) - flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq) + flash_params = struct.pack(b"BB", flash_mode, flash_size + flash_freq) if flash_params != image[2:4]: - print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params)) + print("Flash params set to 0x%04x" % struct.unpack(">H", flash_params)) image = image[0:2] + flash_params + image[4:] return image @@ -218,25 +286,30 @@ def write_flash(esp, args): if args.compress is None and not args.no_compress: args.compress = not args.no_stub - # In case we have encrypted files to write, we first do few sanity checks before actual flash + # In case we have encrypted files to write, + # we first do few sanity checks before actual flash if args.encrypt or args.encrypt_files is not None: do_write = True if not esp.secure_download_mode: if esp.get_encrypted_download_disabled(): - raise FatalError("This chip has encrypt functionality in UART download mode disabled. " - "This is the Flash Encryption configuration for Production mode instead of Development mode.") + raise FatalError( + "This chip has encrypt functionality " + "in UART download mode disabled. " + "This is the Flash Encryption configuration for Production mode " + "instead of Development mode." + ) crypt_cfg_efuse = esp.get_flash_crypt_config() if crypt_cfg_efuse is not None and crypt_cfg_efuse != 0xF: - print('Unexpected FLASH_CRYPT_CONFIG value: 0x%x' % (crypt_cfg_efuse)) + print("Unexpected FLASH_CRYPT_CONFIG value: 0x%x" % (crypt_cfg_efuse)) do_write = False enc_key_valid = esp.is_flash_encryption_key_valid() if not enc_key_valid: - print('Flash encryption key is not programmed') + print("Flash encryption key is not programmed") do_write = False # Determine which files list contain the ones to encrypt @@ -244,22 +317,30 @@ def write_flash(esp, args): for address, argfile in files_to_encrypt: if address % esp.FLASH_ENCRYPTED_WRITE_ALIGN: - print("File %s address 0x%x is not %d byte aligned, can't flash encrypted" % - (argfile.name, address, esp.FLASH_ENCRYPTED_WRITE_ALIGN)) + print( + "File %s address 0x%x is not %d byte aligned, can't flash encrypted" + % (argfile.name, address, esp.FLASH_ENCRYPTED_WRITE_ALIGN) + ) do_write = False if not do_write and not args.ignore_flash_encryption_efuse_setting: - raise FatalError("Can't perform encrypted flash write, consult Flash Encryption documentation for more information") + raise FatalError( + "Can't perform encrypted flash write, " + "consult Flash Encryption documentation for more information" + ) # verify file sizes fit in flash - if args.flash_size != 'keep': # TODO: check this even with 'keep' + if args.flash_size != "keep": # TODO: check this even with 'keep' flash_end = flash_size_bytes(args.flash_size) for address, argfile in args.addr_filename: argfile.seek(0, os.SEEK_END) if address + argfile.tell() > flash_end: - raise FatalError(("File %s (length %d) at offset %d will not fit in %d bytes of flash. " - "Use --flash_size argument, or change flashing address.") - % (argfile.name, argfile.tell(), address, flash_end)) + raise FatalError( + "File %s (length %d) at offset %d " + "will not fit in %d bytes of flash. " + "Use --flash_size argument, or change flashing address." + % (argfile.name, argfile.tell(), address, flash_end) + ) argfile.seek(0) if args.erase_all: @@ -271,28 +352,47 @@ def write_flash(esp, args): argfile.seek(0) bytes_over = address % esp.FLASH_SECTOR_SIZE if bytes_over != 0: - print("WARNING: Flash address {:#010x} is not aligned to a {:#x} byte flash sector. " - "{:#x} bytes before this address will be erased." - .format(address, esp.FLASH_SECTOR_SIZE, bytes_over)) + print( + "WARNING: Flash address {:#010x} is not aligned " + "to a {:#x} byte flash sector. " + "{:#x} bytes before this address will be erased.".format( + address, esp.FLASH_SECTOR_SIZE, bytes_over + ) + ) # Print the address range of to-be-erased flash memory region - print("Flash will be erased from {:#010x} to {:#010x}..." - .format(address - bytes_over, div_roundup(write_end, esp.FLASH_SECTOR_SIZE) * esp.FLASH_SECTOR_SIZE - 1)) + print( + "Flash will be erased from {:#010x} to {:#010x}...".format( + address - bytes_over, + div_roundup(write_end, esp.FLASH_SECTOR_SIZE) + * esp.FLASH_SECTOR_SIZE + - 1, + ) + ) - """ Create a list describing all the files we have to flash. Each entry holds an "encrypt" flag - marking whether the file needs encryption or not. This list needs to be sorted. + """ Create a list describing all the files we have to flash. + Each entry holds an "encrypt" flag marking whether the file needs encryption or not. + This list needs to be sorted. First, append to each entry of our addr_filename list the flag args.encrypt - For example, if addr_filename is [(0x1000, "partition.bin"), (0x8000, "bootloader")], - all_files will be [(0x1000, "partition.bin", args.encrypt), (0x8000, "bootloader", args.encrypt)], + E.g., if addr_filename is [(0x1000, "partition.bin"), (0x8000, "bootloader")], + all_files will be [ + (0x1000, "partition.bin", args.encrypt), + (0x8000, "bootloader", args.encrypt) + ], where, of course, args.encrypt is either True or False """ - all_files = [(offs, filename, args.encrypt) for (offs, filename) in args.addr_filename] + all_files = [ + (offs, filename, args.encrypt) for (offs, filename) in args.addr_filename + ] - """Now do the same with encrypt_files list, if defined. + """ + Now do the same with encrypt_files list, if defined. In this case, the flag is True """ if args.encrypt_files is not None: - encrypted_files_flag = [(offs, filename, True) for (offs, filename) in args.encrypt_files] + encrypted_files_flag = [ + (offs, filename, True) for (offs, filename) in args.encrypt_files + ] # Concatenate both lists and sort them. # As both list are already sorted, we could simply do a merge instead, @@ -305,15 +405,17 @@ def write_flash(esp, args): # Check whether we can compress the current file before flashing if compress and encrypted: - print('\nWARNING: - compress and encrypt options are mutually exclusive ') - print('Will flash %s uncompressed' % argfile.name) + print("\nWARNING: - compress and encrypt options are mutually exclusive ") + print("Will flash %s uncompressed" % argfile.name) compress = False if args.no_stub: - print('Erasing flash...') - image = pad_to(argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4) + print("Erasing flash...") + image = pad_to( + argfile.read(), esp.FLASH_ENCRYPTED_WRITE_ALIGN if encrypted else 4 + ) if len(image) == 0: - print('WARNING: File %s is empty' % argfile.name) + print("WARNING: File %s is empty" % argfile.name) continue image = _update_image_flash_params(esp, address, args, image) calcmd5 = hashlib.md5(image).hexdigest() @@ -321,8 +423,8 @@ def write_flash(esp, args): if compress: uncimage = image image = zlib.compress(uncimage, 9) - # Decompress the compressed binary a block at a time, to dynamically calculate the - # timeout based on the real write size + # Decompress the compressed binary a block at a time, + # to dynamically calculate the timeout based on the real write size decompress = zlib.decompressobj() blocks = esp.flash_defl_begin(uncsize, len(image), address) else: @@ -336,34 +438,46 @@ def write_flash(esp, args): timeout = DEFAULT_TIMEOUT while len(image) > 0: - print_overwrite('Writing at 0x%08x... (%d %%)' % (address + bytes_written, 100 * (seq + 1) // blocks)) + print_overwrite( + "Writing at 0x%08x... (%d %%)" + % (address + bytes_written, 100 * (seq + 1) // blocks) + ) sys.stdout.flush() - block = image[0:esp.FLASH_WRITE_SIZE] + block = image[0 : esp.FLASH_WRITE_SIZE] if compress: - # feeding each compressed block into the decompressor lets us see block-by-block how much will be written + # feeding each compressed block into the decompressor lets us + # see block-by-block how much will be written block_uncompressed = len(decompress.decompress(block)) bytes_written += block_uncompressed - block_timeout = max(DEFAULT_TIMEOUT, timeout_per_mb(ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed)) + block_timeout = max( + DEFAULT_TIMEOUT, + timeout_per_mb(ERASE_WRITE_TIMEOUT_PER_MB, block_uncompressed), + ) if not esp.IS_STUB: - timeout = block_timeout # ROM code writes block to flash before ACKing + timeout = ( + block_timeout # ROM code writes block to flash before ACKing + ) esp.flash_defl_block(block, seq, timeout=timeout) if esp.IS_STUB: - timeout = block_timeout # Stub ACKs when block is received, then writes to flash while receiving the block after it + # Stub ACKs when block is received, + # then writes to flash while receiving the block after it + timeout = block_timeout else: # Pad the last block - block = block + b'\xff' * (esp.FLASH_WRITE_SIZE - len(block)) + block = block + b"\xff" * (esp.FLASH_WRITE_SIZE - len(block)) if encrypted: esp.flash_encrypt_block(block, seq) else: esp.flash_block(block, seq) bytes_written += len(block) bytes_sent += len(block) - image = image[esp.FLASH_WRITE_SIZE:] + image = image[esp.FLASH_WRITE_SIZE :] seq += 1 if esp.IS_STUB: - # Stub only writes each block to flash after 'ack'ing the receive, so do a final dummy operation which will - # not be 'ack'ed until the last block has actually been written out to flash + # Stub only writes each block to flash after 'ack'ing the receive, + # so do a final dummy operation which will not be 'ack'ed + # until the last block has actually been written out to flash esp.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR, timeout=timeout) t = time.time() - t @@ -371,28 +485,37 @@ def write_flash(esp, args): if compress: if t > 0.0: speed_msg = " (effective %.1f kbit/s)" % (uncsize / t * 8 / 1000) - print_overwrite('Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s...' % (uncsize, - bytes_sent, - address, t, speed_msg), last_line=True) + print_overwrite( + "Wrote %d bytes (%d compressed) at 0x%08x in %.1f seconds%s..." + % (uncsize, bytes_sent, address, t, speed_msg), + last_line=True, + ) else: if t > 0.0: speed_msg = " (%.1f kbit/s)" % (bytes_written / t * 8 / 1000) - print_overwrite('Wrote %d bytes at 0x%08x in %.1f seconds%s...' % (bytes_written, address, t, speed_msg), last_line=True) + print_overwrite( + "Wrote %d bytes at 0x%08x in %.1f seconds%s..." + % (bytes_written, address, t, speed_msg), + last_line=True, + ) if not encrypted and not esp.secure_download_mode: try: res = esp.flash_md5sum(address, uncsize) if res != calcmd5: - print('File md5: %s' % calcmd5) - print('Flash md5: %s' % res) - print('MD5 of 0xFF is %s' % (hashlib.md5(b'\xFF' * uncsize).hexdigest())) + print("File md5: %s" % calcmd5) + print("Flash md5: %s" % res) + print( + "MD5 of 0xFF is %s" + % (hashlib.md5(b"\xFF" * uncsize).hexdigest()) + ) raise FatalError("MD5 of file does not match data in flash!") else: - print('Hash of data verified.') + print("Hash of data verified.") except NotImplementedInROMError: pass - print('\nLeaving...') + print("\nLeaving...") if esp.IS_STUB: # skip sending flash_finish to ROM loader here, @@ -411,12 +534,17 @@ def write_flash(esp, args): esp.flash_finish(False) if args.verify: - print('Verifying just-written flash...') - print('(This option is deprecated, flash contents are now always read back after flashing.)') - # If some encrypted files have been flashed print a warning saying that we won't check them + print("Verifying just-written flash...") + print( + "(This option is deprecated, " + "flash contents are now always read back after flashing.)" + ) + # If some encrypted files have been flashed, + # print a warning saying that we won't check them if args.encrypt or args.encrypt_files is not None: - print('WARNING: - cannot verify encrypted files, they will be ignored') - # Call verify_flash function only if there at least one non-encrypted file flashed + print("WARNING: - cannot verify encrypted files, they will be ignored") + # Call verify_flash function only if there is at least + # one non-encrypted file flashed if not args.encrypt: verify_flash(esp, args) @@ -425,26 +553,39 @@ def image_info(args): if args.chip == "auto": print("WARNING: --chip not specified, defaulting to ESP8266.") image = LoadFirmwareImage(args.chip, args.filename) - print('Image version: %d' % image.version) - print('Entry point: %08x' % image.entrypoint if image.entrypoint != 0 else 'Entry point not set') - print('%d segments' % len(image.segments)) + print("Image version: %d" % image.version) + print( + "Entry point: %08x" % image.entrypoint + if image.entrypoint != 0 + else "Entry point not set" + ) + print("%d segments" % len(image.segments)) print() idx = 0 for seg in image.segments: idx += 1 segs = seg.get_memory_type(image) seg_name = ",".join(segs) - print('Segment %d: %r [%s]' % (idx, seg, seg_name)) + print("Segment %d: %r [%s]" % (idx, seg, seg_name)) calc_checksum = image.calculate_checksum() - print('Checksum: %02x (%s)' % (image.checksum, - 'valid' if image.checksum == calc_checksum else 'invalid - calculated %02x' % calc_checksum)) + print( + "Checksum: %02x (%s)" + % ( + image.checksum, + "valid" + if image.checksum == calc_checksum + else "invalid - calculated %02x" % calc_checksum, + ) + ) try: - digest_msg = 'Not appended' + digest_msg = "Not appended" if image.append_digest: is_valid = image.stored_digest == image.calc_digest - digest_msg = "%s (%s)" % (hexify(image.calc_digest).lower(), - "valid" if is_valid else "invalid") - print('Validation Hash: %s' % digest_msg) + digest_msg = "%s (%s)" % ( + hexify(image.calc_digest).lower(), + "valid" if is_valid else "invalid", + ) + print("Validation Hash: %s" % digest_msg) except AttributeError: pass # ESP8266 image has no append_digest field @@ -452,11 +593,13 @@ def image_info(args): def make_image(args): image = ESP8266ROMFirmwareImage() if len(args.segfile) == 0: - raise FatalError('No segments specified') + raise FatalError("No segments specified") if len(args.segfile) != len(args.segaddr): - raise FatalError('Number of specified files does not match number of specified addresses') + raise FatalError( + "Number of specified files does not match number of specified addresses" + ) for (seg, addr) in zip(args.segfile, args.segaddr): - with open(seg, 'rb') as f: + with open(seg, "rb") as f: data = f.read() image.segments.append(ImageSegment(addr, data)) image.entrypoint = args.entrypoint @@ -465,59 +608,59 @@ def make_image(args): def elf2image(args): e = ELFFile(args.input) - if args.chip == 'auto': # Default to ESP8266 for backwards compatibility - args.chip = 'esp8266' + if args.chip == "auto": # Default to ESP8266 for backwards compatibility + args.chip = "esp8266" print("Creating {} image...".format(args.chip)) - if args.chip == 'esp32': + if args.chip == "esp32": image = ESP32FirmwareImage() if args.secure_pad: - image.secure_pad = '1' + image.secure_pad = "1" elif args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32s2': + image.secure_pad = "2" + elif args.chip == "esp32s2": image = ESP32S2FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32s3beta2': + image.secure_pad = "2" + elif args.chip == "esp32s3beta2": image = ESP32S3BETA2FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32s3': + image.secure_pad = "2" + elif args.chip == "esp32s3": image = ESP32S3FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32c3': + image.secure_pad = "2" + elif args.chip == "esp32c3": image = ESP32C3FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32c6beta': + image.secure_pad = "2" + elif args.chip == "esp32c6beta": image = ESP32C6BETAFirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32h2beta1': + image.secure_pad = "2" + elif args.chip == "esp32h2beta1": image = ESP32H2BETA1FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32h2beta2': + image.secure_pad = "2" + elif args.chip == "esp32h2beta2": image = ESP32H2BETA2FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.chip == 'esp32c2': + image.secure_pad = "2" + elif args.chip == "esp32c2": image = ESP32C2FirmwareImage() if args.secure_pad_v2: - image.secure_pad = '2' - elif args.version == '1': # ESP8266 + image.secure_pad = "2" + elif args.version == "1": # ESP8266 image = ESP8266ROMFirmwareImage() - elif args.version == '2': + elif args.version == "2": image = ESP8266V2FirmwareImage() else: image = ESP8266V3FirmwareImage() image.entrypoint = e.entrypoint image.flash_mode = FLASH_MODES[args.flash_mode] - if args.chip != 'esp8266': + if args.chip != "esp8266": image.min_rev = int(args.min_rev) if args.flash_mmu_page_size: @@ -552,31 +695,32 @@ def read_mac(esp, args): mac = esp.read_mac() def print_mac(label, mac): - print('%s: %s' % (label, ':'.join(map(lambda x: '%02x' % x, mac)))) + print("%s: %s" % (label, ":".join(map(lambda x: "%02x" % x, mac)))) + print_mac("MAC", mac) def chip_id(esp, args): try: chipid = esp.chip_id() - print('Chip ID: 0x%08x' % chipid) + print("Chip ID: 0x%08x" % chipid) except NotSupportedError: - print('Warning: %s has no Chip ID. Reading MAC instead.' % esp.CHIP_NAME) + print("Warning: %s has no Chip ID. Reading MAC instead." % esp.CHIP_NAME) read_mac(esp, args) def erase_flash(esp, args): - print('Erasing flash (this may take a while)...') + print("Erasing flash (this may take a while)...") t = time.time() esp.erase_flash() - print('Chip erase completed successfully in %.1fs' % (time.time() - t)) + print("Chip erase completed successfully in %.1fs" % (time.time() - t)) def erase_region(esp, args): - print('Erasing region (may be slow depending on size)...') + print("Erasing region (may be slow depending on size)...") t = time.time() esp.erase_region(args.address, args.size) - print('Erase completed successfully in %.1f seconds.' % (time.time() - t)) + print("Erase completed successfully in %.1f seconds." % (time.time() - t)) def run(esp, args): @@ -585,29 +729,36 @@ def run(esp, args): def flash_id(esp, args): flash_id = esp.flash_id() - print('Manufacturer: %02x' % (flash_id & 0xff)) + print("Manufacturer: %02x" % (flash_id & 0xFF)) flid_lowbyte = (flash_id >> 16) & 0xFF - print('Device: %02x%02x' % ((flash_id >> 8) & 0xff, flid_lowbyte)) - print('Detected flash size: %s' % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown"))) + print("Device: %02x%02x" % ((flash_id >> 8) & 0xFF, flid_lowbyte)) + print( + "Detected flash size: %s" % (DETECTED_FLASH_SIZES.get(flid_lowbyte, "Unknown")) + ) def read_flash(esp, args): if args.no_progress: flash_progress = None else: + def flash_progress(progress, length): - msg = '%d (%d %%)' % (progress, progress * 100.0 / length) - padding = '\b' * len(msg) + msg = "%d (%d %%)" % (progress, progress * 100.0 / length) + padding = "\b" * len(msg) if progress == length: - padding = '\n' + padding = "\n" sys.stdout.write(msg + padding) sys.stdout.flush() + t = time.time() data = esp.read_flash(args.address, args.size, flash_progress) t = time.time() - t - print_overwrite('Read %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)...' - % (len(data), args.address, t, len(data) / t * 8 / 1000), last_line=True) - with open(args.filename, 'wb') as f: + print_overwrite( + "Read %d bytes at 0x%x in %.1f seconds (%.1f kbit/s)..." + % (len(data), args.address, t, len(data) / t * 8 / 1000), + last_line=True, + ) + with open(args.filename, "wb") as f: f.write(data) @@ -621,81 +772,99 @@ def verify_flash(esp, args): image = _update_image_flash_params(esp, address, args, image) image_size = len(image) - print('Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s...' % (image_size, image_size, address, argfile.name)) + print( + "Verifying 0x%x (%d) bytes @ 0x%08x in flash against %s..." + % (image_size, image_size, address, argfile.name) + ) # Try digest first, only read if there are differences. digest = esp.flash_md5sum(address, image_size) expected_digest = hashlib.md5(image).hexdigest() if digest == expected_digest: - print('-- verify OK (digest matched)') + print("-- verify OK (digest matched)") continue else: differences = True - if getattr(args, 'diff', 'no') != 'yes': - print('-- verify FAILED (digest mismatch)') + if getattr(args, "diff", "no") != "yes": + print("-- verify FAILED (digest mismatch)") continue flash = esp.read_flash(address, image_size) assert flash != image diff = [i for i in range(image_size) if flash[i] != image[i]] - print('-- verify FAILED: %d differences, first @ 0x%08x' % (len(diff), address + diff[0])) + print( + "-- verify FAILED: %d differences, first @ 0x%08x" + % (len(diff), address + diff[0]) + ) for d in diff: flash_byte = flash[d] image_byte = image[d] if PYTHON2: flash_byte = ord(flash_byte) image_byte = ord(image_byte) - print(' %08x %02x %02x' % (address + d, flash_byte, image_byte)) + print(" %08x %02x %02x" % (address + d, flash_byte, image_byte)) if differences: raise FatalError("Verify failed.") def read_flash_status(esp, args): - print('Status value: 0x%04x' % esp.read_status(args.bytes)) + print("Status value: 0x%04x" % esp.read_status(args.bytes)) def write_flash_status(esp, args): fmt = "0x%%0%dx" % (args.bytes * 2) args.value = args.value & ((1 << (args.bytes * 8)) - 1) - print(('Initial flash status: ' + fmt) % esp.read_status(args.bytes)) - print(('Setting flash status: ' + fmt) % args.value) + print(("Initial flash status: " + fmt) % esp.read_status(args.bytes)) + print(("Setting flash status: " + fmt) % args.value) esp.write_status(args.value, args.bytes, args.non_volatile) - print(('After flash status: ' + fmt) % esp.read_status(args.bytes)) + print(("After flash status: " + fmt) % esp.read_status(args.bytes)) def get_security_info(esp, args): si = esp.get_security_info() # TODO: better display and tests - print('Flags: {:#010x} ({})'.format(si["flags"], bin(si["flags"]))) - print('Flash_Crypt_Cnt: {:#x}'.format(si["flash_crypt_cnt"])) - print('Key_Purposes: {}'.format(si["key_purposes"])) + print("Flags: {:#010x} ({})".format(si["flags"], bin(si["flags"]))) + print("Flash_Crypt_Cnt: {:#x}".format(si["flash_crypt_cnt"])) + print("Key_Purposes: {}".format(si["key_purposes"])) if si["chip_id"] is not None and si["api_version"] is not None: - print('Chip_ID: {}'.format(si["chip_id"])) - print('Api_Version: {}'.format(si["api_version"])) + print("Chip_ID: {}".format(si["chip_id"])) + print("Api_Version: {}".format(si["api_version"])) def merge_bin(args): try: chip_class = CHIP_DEFS[args.chip] except KeyError: - msg = "Please specify the chip argument" if args.chip == "auto" else "Invalid chip choice: '{}'".format(args.chip) - msg = msg + " (choose from {})".format(', '.join(CHIP_LIST)) + msg = ( + "Please specify the chip argument" + if args.chip == "auto" + else "Invalid chip choice: '{}'".format(args.chip) + ) + msg = msg + " (choose from {})".format(", ".join(CHIP_LIST)) raise FatalError(msg) - # sort the files by offset. The AddrFilenamePairAction has already checked for overlap + # sort the files by offset. + # The AddrFilenamePairAction has already checked for overlap input_files = sorted(args.addr_filename, key=lambda x: x[0]) if not input_files: raise FatalError("No input files specified") first_addr = input_files[0][0] if first_addr < args.target_offset: - raise FatalError("Output file target offset is 0x%x. Input file offset 0x%x is before this." % (args.target_offset, first_addr)) + raise FatalError( + "Output file target offset is 0x%x. Input file offset 0x%x is before this." + % (args.target_offset, first_addr) + ) - if args.format != 'raw': - raise FatalError("This version of esptool only supports the 'raw' output format") + if args.format != "raw": + raise FatalError( + "This version of esptool only supports the 'raw' output format" + ) + + with open(args.output, "wb") as of: - with open(args.output, 'wb') as of: def pad_to(flash_offs): # account for output file offset if there is any - of.write(b'\xFF' * (flash_offs - args.target_offset - of.tell())) + of.write(b"\xFF" * (flash_offs - args.target_offset - of.tell())) + for addr, argfile in input_files: pad_to(addr) image = argfile.read() @@ -703,9 +872,13 @@ def merge_bin(args): of.write(image) if args.fill_flash_size: pad_to(flash_size_bytes(args.fill_flash_size)) - print("Wrote 0x%x bytes to file %s, ready to flash to offset 0x%x" % (of.tell(), args.output, args.target_offset)) + print( + "Wrote 0x%x bytes to file %s, ready to flash to offset 0x%x" + % (of.tell(), args.output, args.target_offset) + ) def version(args): from . import __version__ + print(__version__) diff --git a/esptool/loader.py b/esptool/loader.py index 43c2d75..f1eda98 100644 --- a/esptool/loader.py +++ b/esptool/loader.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python -# -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -21,52 +20,63 @@ from .util import byte, hexify, mask_to_shift, pad_to try: import serial except ImportError: - print("Pyserial is not installed for %s. Check the README for installation instructions." % (sys.executable)) + print( + "Pyserial is not installed for %s. " + "Check the README for installation instructions." % (sys.executable) + ) raise -# check 'serial' is 'pyserial' and not 'serial' https://github.com/espressif/esptool/issues/269 +# check 'serial' is 'pyserial' and not 'serial' +# ref. https://github.com/espressif/esptool/issues/269 try: if "serialization" in serial.__doc__ and "deserialization" in serial.__doc__: - raise ImportError(""" -esptool.py depends on pyserial, but there is a conflict with a currently installed package named 'serial'. - -You may be able to work around this by 'pip uninstall serial; pip install pyserial' \ -but this may break other installed Python software that depends on 'serial'. - -There is no good fix for this right now, apart from configuring virtualenvs. \ -See https://github.com/espressif/esptool/issues/269#issuecomment-385298196 for discussion of the underlying issue(s).""") + raise ImportError( + "esptool.py depends on pyserial, but there is a conflict with a currently " + "installed package named 'serial'.\n" + "You may work around this by 'pip uninstall serial; pip install pyserial' " + "but this may break other installed Python software " + "that depends on 'serial'.\n" + "There is no good fix for this right now, " + "apart from configuring virtualenvs. " + "See https://github.com/espressif/esptool/issues/269#issuecomment-385298196" + " for discussion of the underlying issue(s)." + ) except TypeError: pass # __doc__ returns None for pyserial try: import serial.tools.list_ports as list_ports except ImportError: - print("The installed version (%s) of pyserial appears to be too old for esptool.py (Python interpreter %s). " - "Check the README for installation instructions." % (sys.VERSION, sys.executable)) + print( + "The installed version (%s) of pyserial appears to be too old for esptool.py " + "(Python interpreter %s). Check the README for installation instructions." + % (sys.VERSION, sys.executable) + ) raise except Exception: if sys.platform == "darwin": - # swallow the exception, this is a known issue in pyserial+macOS Big Sur preview ref https://github.com/espressif/esptool/issues/540 + # swallow the exception, this is a known issue in pyserial+macOS Big Sur preview + # ref https://github.com/espressif/esptool/issues/540 list_ports = None else: raise -DEFAULT_TIMEOUT = 3 # timeout for most flash operations -START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase) -CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase +DEFAULT_TIMEOUT = 3 # timeout for most flash operations +START_FLASH_TIMEOUT = 20 # timeout for starting flash (may perform erase) +CHIP_ERASE_TIMEOUT = 120 # timeout for full chip erase MAX_TIMEOUT = CHIP_ERASE_TIMEOUT * 2 # longest any command can run -SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader -MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum -ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region -ERASE_WRITE_TIMEOUT_PER_MB = 40 # timeout (per megabyte) for erasing and writing data -MEM_END_ROM_TIMEOUT = 0.05 # special short timeout for ESP_MEM_END, as it may never respond -DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write -DEFAULT_CONNECT_ATTEMPTS = 7 # default number of times to try connection +SYNC_TIMEOUT = 0.1 # timeout for syncing with bootloader +MD5_TIMEOUT_PER_MB = 8 # timeout (per megabyte) for calculating md5sum +ERASE_REGION_TIMEOUT_PER_MB = 30 # timeout (per megabyte) for erasing a region +ERASE_WRITE_TIMEOUT_PER_MB = 40 # timeout (per megabyte) for erasing and writing data +MEM_END_ROM_TIMEOUT = 0.05 # short timeout for ESP_MEM_END, as it may never respond +DEFAULT_SERIAL_WRITE_TIMEOUT = 10 # timeout for serial port write +DEFAULT_CONNECT_ATTEMPTS = 7 # default number of times to try connection def timeout_per_mb(seconds_per_mb, size_bytes): - """ Scales timeouts which are size-specific """ + """Scales timeouts which are size-specific""" result = seconds_per_mb * (size_bytes / 1e6) if result < DEFAULT_TIMEOUT: return DEFAULT_TIMEOUT @@ -83,28 +93,34 @@ def check_supported_function(func, check_func): software stub that runs on these. Not possible to do this cleanly via inheritance alone. """ + def inner(*args, **kwargs): obj = args[0] if check_func(obj): return func(*args, **kwargs) else: raise NotImplementedInROMError(obj, func) + return inner def stub_function_only(func): - """ Attribute for a function only supported in the software stub loader """ + """Attribute for a function only supported in the software stub loader""" return check_supported_function(func, lambda o: o.IS_STUB) def stub_and_esp32_function_only(func): - """ Attribute for a function only supported by software stubs or ESP32 and later chips ROM """ - return check_supported_function(func, lambda o: o.IS_STUB or o.CHIP_NAME not in ["ESP8266"]) + """Attribute for a function only supported by stubs or ESP32 and later chips ROM""" + return check_supported_function( + func, lambda o: o.IS_STUB or o.CHIP_NAME not in ["ESP8266"] + ) def esp32s3_or_newer_function_only(func): - """ Attribute for a function only supported by ESP32S3 and later chips ROM """ - return check_supported_function(func, lambda o: o.CHIP_NAME not in ["ESP8266", "ESP32", "ESP32-S2"]) + """Attribute for a function only supported by ESP32S3 and later chips ROM""" + return check_supported_function( + func, lambda o: o.CHIP_NAME not in ["ESP8266", "ESP32", "ESP32-S2"] + ) # Provide a 'basestring' class on Python 3 @@ -115,7 +131,7 @@ except NameError: class ESPLoader(object): - """ Base class providing access to ESP ROM & software stub bootloaders. + """Base class providing access to ESP ROM & software stub bootloaders. Subclasses provide ESP8266 & ESP32 Family specific functionality. Don't instantiate this base class directly, either instantiate a subclass or @@ -123,6 +139,7 @@ class ESPLoader(object): appropriate subclass instance. """ + CHIP_NAME = "Espressif device" IS_STUB = False @@ -134,24 +151,24 @@ class ESPLoader(object): # Commands supported by ESP8266 ROM bootloader ESP_FLASH_BEGIN = 0x02 - ESP_FLASH_DATA = 0x03 - ESP_FLASH_END = 0x04 - ESP_MEM_BEGIN = 0x05 - ESP_MEM_END = 0x06 - ESP_MEM_DATA = 0x07 - ESP_SYNC = 0x08 - ESP_WRITE_REG = 0x09 - ESP_READ_REG = 0x0a + ESP_FLASH_DATA = 0x03 + ESP_FLASH_END = 0x04 + ESP_MEM_BEGIN = 0x05 + ESP_MEM_END = 0x06 + ESP_MEM_DATA = 0x07 + ESP_SYNC = 0x08 + ESP_WRITE_REG = 0x09 + ESP_READ_REG = 0x0A # Some comands supported by ESP32 and later chips ROM bootloader (or -8266 w/ stub) ESP_SPI_SET_PARAMS = 0x0B - ESP_SPI_ATTACH = 0x0D - ESP_READ_FLASH_SLOW = 0x0e # ROM only, much slower than the stub flash read + ESP_SPI_ATTACH = 0x0D + ESP_READ_FLASH_SLOW = 0x0E # ROM only, much slower than the stub flash read ESP_CHANGE_BAUDRATE = 0x0F ESP_FLASH_DEFL_BEGIN = 0x10 - ESP_FLASH_DEFL_DATA = 0x11 - ESP_FLASH_DEFL_END = 0x12 - ESP_SPI_FLASH_MD5 = 0x13 + ESP_FLASH_DEFL_DATA = 0x11 + ESP_FLASH_DEFL_END = 0x12 + ESP_SPI_FLASH_MD5 = 0x13 # Commands supported by ESP32-S2 and later chips ROM bootloader only ESP_GET_SECURITY_INFO = 0x14 @@ -166,28 +183,29 @@ class ESPLoader(object): ESP_FLASH_ENCRYPT_DATA = 0xD4 # Response code(s) sent by ROM - ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received + ROM_INVALID_RECV_MSG = 0x05 # response if an invalid message is received # Maximum block sized for RAM and Flash writes, respectively. - ESP_RAM_BLOCK = 0x1800 + ESP_RAM_BLOCK = 0x1800 FLASH_WRITE_SIZE = 0x400 # Default baudrate. The ROM auto-bauds, so we can use more or less whatever we want. - ESP_ROM_BAUD = 115200 + ESP_ROM_BAUD = 115200 # First byte of the application image - ESP_IMAGE_MAGIC = 0xe9 + ESP_IMAGE_MAGIC = 0xE9 # Initial state for the checksum routine - ESP_CHECKSUM_MAGIC = 0xef + ESP_CHECKSUM_MAGIC = 0xEF # Flash sector size, minimum unit of erase. FLASH_SECTOR_SIZE = 0x1000 UART_DATE_REG_ADDR = 0x60000078 - CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000 # This ROM address has a different value on each chip model + # This ROM address has a different value on each chip model + CHIP_DETECT_MAGIC_REG_ADDR = 0x40001000 UART_CLKDIV_MASK = 0xFFFFF @@ -204,7 +222,8 @@ class ESPLoader(object): # ROM supports an encrypted flashing mode SUPPORTS_ENCRYPTED_FLASH = False - # Response to ESP_SYNC might indicate that flasher stub is running instead of the ROM bootloader + # Response to ESP_SYNC might indicate that flasher stub is running + # instead of the ROM bootloader sync_stub_detected = False # Device PIDs @@ -225,8 +244,10 @@ class ESPLoader(object): with ones which throw NotImplementedInROMError(). """ - self.secure_download_mode = False # flag is set to True if esptool detects the ROM is in Secure Download Mode - self.stub_is_disabled = False # flag is set to True if esptool detects conditions which require the stub to be disabled + # True if esptool detects the ROM is in Secure Download Mode + self.secure_download_mode = False + # True if esptool detects conditions which require the stub to be disabled + self.stub_is_disabled = False if isinstance(port, basestring): self._port = serial.serial_for_url(port) @@ -255,17 +276,22 @@ class ESPLoader(object): try: self._port.baudrate = baud except IOError: - raise FatalError("Failed to set baud rate %d. The driver may not support this rate." % baud) + raise FatalError( + "Failed to set baud rate %d. The driver may not support this rate." + % baud + ) - """ Read a SLIP packet from the serial port """ def read(self): + """Read a SLIP packet from the serial port""" return next(self._slip_reader) - """ Write bytes to the serial port while performing SLIP escaping """ def write(self, packet): - buf = b'\xc0' \ - + (packet.replace(b'\xdb', b'\xdb\xdd').replace(b'\xc0', b'\xdb\xdc')) \ - + b'\xc0' + """Write bytes to the serial port while performing SLIP escaping""" + buf = ( + b"\xc0" + + (packet.replace(b"\xdb", b"\xdb\xdd").replace(b"\xc0", b"\xdb\xdc")) + + b"\xc0" + ) self.trace("Write %d bytes: %s", len(buf), HexFormatter(buf)) self._port.write(buf) @@ -281,9 +307,9 @@ class ESPLoader(object): prefix = "TRACE +%.3f " % delta print(prefix + (message % format_args)) - """ Calculate checksum of a blob, as it is defined by the ROM """ @staticmethod def checksum(data, state=ESP_CHECKSUM_MAGIC): + """Calculate checksum of a blob, as it is defined by the ROM""" for b in data: if type(b) is int: # python 2/3 compat state ^= b @@ -292,8 +318,15 @@ class ESPLoader(object): return state - """ Send a request and read the response """ - def command(self, op=None, data=b"", chk=0, wait_response=True, timeout=DEFAULT_TIMEOUT): + def command( + self, + op=None, + data=b"", + chk=0, + wait_response=True, + timeout=DEFAULT_TIMEOUT, + ): + """Send a request and read the response""" saved_timeout = self._port.timeout new_timeout = min(timeout, MAX_TIMEOUT) if new_timeout != saved_timeout: @@ -301,9 +334,16 @@ class ESPLoader(object): try: if op is not None: - self.trace("command op=0x%02x data len=%s wait_response=%d timeout=%.3f data=%s", - op, len(data), 1 if wait_response else 0, timeout, HexFormatter(data)) - pkt = struct.pack(b' self.STATUS_BYTES_LENGTH: - return data[:-self.STATUS_BYTES_LENGTH] - else: # otherwise, just return the 'val' field which comes from the reply header (this is used by read_reg) + return data[: -self.STATUS_BYTES_LENGTH] + else: + # otherwise, just return the 'val' field which comes from the reply header + # (this is used by read_reg) return val def flush_input(self): @@ -365,12 +414,13 @@ class ESPLoader(object): self._slip_reader = slip_reader(self._port, self.trace) def sync(self): - val, _ = self.command(self.ESP_SYNC, b'\x07\x07\x12\x20' + 32 * b'\x55', - timeout=SYNC_TIMEOUT) + val, _ = self.command( + self.ESP_SYNC, b"\x07\x07\x12\x20" + 32 * b"\x55", timeout=SYNC_TIMEOUT + ) - # ROM bootloaders send some non-zero "val" response. The flasher stub sends 0. If we receive 0 then it - # probably indicates that the chip wasn't or couldn't be reseted properly and esptool is talking to the - # flasher stub. + # ROM bootloaders send some non-zero "val" response. The flasher stub sends 0. + # If we receive 0 then it probably indicates that the chip wasn't or couldn't be + # reseted properly and esptool is talking to the flasher stub. self.sync_stub_detected = val == 0 for _ in range(7): @@ -389,13 +439,19 @@ class ESPLoader(object): def _get_pid(self): if list_ports is None: - print("\nListing all serial ports is currently not available. Can't get device PID.") + print( + "\nListing all serial ports is currently not available. " + "Can't get device PID." + ) return active_port = self._port.port # Pyserial only identifies regular ports, URL handlers are not supported if not active_port.lower().startswith(("com", "/dev/")): - print("\nDevice PID identification is only supported on COM and /dev/ serial ports.") + print( + "\nDevice PID identification is only supported on " + "COM and /dev/ serial ports." + ) return # Return the real path if the active port is a symlink if active_port.startswith("/dev/") and os.path.islink(active_port): @@ -408,10 +464,14 @@ class ESPLoader(object): for p in ports: if p.device in active_port: return p.pid - print("\nFailed to get PID of a device on {}, using standard reset sequence.".format(active_port)) + print( + "\nFailed to get PID of a device on {}, " + "using standard reset sequence.".format(active_port) + ) def bootloader_reset(self, usb_jtag_serial=False, extra_delay=False): - """ Issue a reset-to-bootloader, with USB-JTAG-Serial custom reset sequence option + """ + Issue a reset-to-bootloader, with USB-JTAG-Serial custom reset sequence option """ # RTS = either CH_PD/EN or nRESET (both active low = chip in reset) # DTR = GPIO0 (active low = boot to flasher) @@ -427,27 +487,40 @@ class ESPLoader(object): self._setDTR(True) # Set IO0 self._setRTS(False) time.sleep(0.1) - self._setRTS(True) # Reset. Note dtr/rts calls inverted so we go through (1,1) instead of (0,0) + self._setRTS( + True + ) # Reset. Note dtr/rts calls inverted to go through (1,1) instead of (0,0) self._setDTR(False) - self._setRTS(True) # Extra RTS set for RTS as Windows only propagates DTR on RTS setting + self._setRTS( + True + ) # Extra RTS set for RTS as Windows only propagates DTR on RTS setting time.sleep(0.1) self._setDTR(False) self._setRTS(False) else: # This fpga delay is for Espressif internal use - fpga_delay = True if self.FPGA_SLOW_BOOT and os.environ.get("ESPTOOL_ENV_FPGA", "").strip() == "1" else False - delay = 7 if fpga_delay else 0.5 if extra_delay else 0.05 # 0.5 needed for ESP32 rev0 and rev1 + fpga_delay = ( + True + if self.FPGA_SLOW_BOOT + and os.environ.get("ESPTOOL_ENV_FPGA", "").strip() == "1" + else False + ) + delay = ( + 7 if fpga_delay else 0.5 if extra_delay else 0.05 + ) # 0.5 needed for ESP32 rev0 and rev1 self._setDTR(False) # IO0=HIGH - self._setRTS(True) # EN=LOW, chip in reset + self._setRTS(True) # EN=LOW, chip in reset time.sleep(0.1) - self._setDTR(True) # IO0=LOW + self._setDTR(True) # IO0=LOW self._setRTS(False) # EN=HIGH, chip out of reset time.sleep(delay) self._setDTR(False) # IO0=HIGH, done - def _connect_attempt(self, mode='default_reset', usb_jtag_serial=False, extra_delay=False): - """ A single connection attempt """ + def _connect_attempt( + self, mode="default_reset", usb_jtag_serial=False, extra_delay=False + ): + """A single connection attempt""" last_error = None boot_log_detected = False download_mode = False @@ -457,15 +530,18 @@ class ESPLoader(object): if mode == "no_reset_no_sync": return last_error - if mode != 'no_reset': + if mode != "no_reset": if not self.USES_RFC2217: # Might block on rfc2217 ports - self._port.reset_input_buffer() # Empty serial buffer to isolate boot log + # Empty serial buffer to isolate boot log + self._port.reset_input_buffer() self.bootloader_reset(usb_jtag_serial, extra_delay) # Detect the ROM boot log and check actual boot mode (ESP32 and later only) waiting = self._port.inWaiting() read_bytes = self._port.read(waiting) - data = re.search(b'boot:(0x[0-9a-fA-F]+)(.*waiting for download)?', read_bytes, re.DOTALL) + data = re.search( + b"boot:(0x[0-9a-fA-F]+)(.*waiting for download)?", read_bytes, re.DOTALL + ) if data is not None: boot_log_detected = True boot_mode = data.group(1) @@ -478,52 +554,83 @@ class ESPLoader(object): self.sync() return None except FatalError as e: - print('.', end='') + print(".", end="") sys.stdout.flush() time.sleep(0.05) last_error = e if boot_log_detected: - last_error = FatalError("Wrong boot mode detected ({})! The chip needs to be in download mode.".format(boot_mode.decode("utf-8"))) + last_error = FatalError( + "Wrong boot mode detected ({})! " + "The chip needs to be in download mode.".format( + boot_mode.decode("utf-8") + ) + ) if download_mode: - last_error = FatalError("Download mode successfully detected, but getting no sync reply: The serial TX path seems to be down.") + last_error = FatalError( + "Download mode successfully detected, but getting no sync reply: " + "The serial TX path seems to be down." + ) return last_error def get_memory_region(self, name): - """ Returns a tuple of (start, end) for the memory map entry with the given name, or None if it doesn't exist + """ + Returns a tuple of (start, end) for the memory map entry with the given name, + or None if it doesn't exist """ try: return [(start, end) for (start, end, n) in self.MEMORY_MAP if n == name][0] except IndexError: return None - def connect(self, mode='default_reset', attempts=DEFAULT_CONNECT_ATTEMPTS, detecting=False, warnings=True): - """ Try connecting repeatedly until successful, or giving up """ - if warnings and mode in ['no_reset', 'no_reset_no_sync']: - print('WARNING: Pre-connection option "{}" was selected.'.format(mode), - 'Connection may fail if the chip is not in bootloader or flasher stub mode.') - print('Connecting...', end='') + def connect( + self, + mode="default_reset", + attempts=DEFAULT_CONNECT_ATTEMPTS, + detecting=False, + warnings=True, + ): + """Try connecting repeatedly until successful, or giving up""" + if warnings and mode in ["no_reset", "no_reset_no_sync"]: + print( + 'WARNING: Pre-connection option "{}" was selected.'.format(mode), + "Connection may fail if the chip is not in bootloader " + "or flasher stub mode.", + ) + print("Connecting...", end="") sys.stdout.flush() last_error = None - usb_jtag_serial = (mode == 'usb_reset') or (self._get_pid() == self.USB_JTAG_SERIAL_PID) + usb_jtag_serial = (mode == "usb_reset") or ( + self._get_pid() == self.USB_JTAG_SERIAL_PID + ) try: - for _, extra_delay in zip(range(attempts) if attempts > 0 else itertools.count(), itertools.cycle((False, True))): - last_error = self._connect_attempt(mode=mode, usb_jtag_serial=usb_jtag_serial, extra_delay=extra_delay) + for _, extra_delay in zip( + range(attempts) if attempts > 0 else itertools.count(), + itertools.cycle((False, True)), + ): + last_error = self._connect_attempt( + mode=mode, usb_jtag_serial=usb_jtag_serial, extra_delay=extra_delay + ) if last_error is None: break finally: - print('') # end 'Connecting...' line + print("") # end 'Connecting...' line if last_error is not None: - raise FatalError('Failed to connect to {}: {}' - '\nFor troubleshooting steps visit: ' - 'https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html'.format(self.CHIP_NAME, last_error)) + raise FatalError( + "Failed to connect to {}: {}" + "\nFor troubleshooting steps visit: " + "https://docs.espressif.com/projects/esptool/en/latest/troubleshooting.html".format( # noqa E501 + self.CHIP_NAME, last_error + ) + ) if not detecting: try: from .targets import ROM_LIST + # check the date code registers match what we expect to see chip_magic_value = self.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR) if chip_magic_value not in self.CHIP_DETECT_MAGIC_VALUE: @@ -533,10 +640,17 @@ class ESPLoader(object): actually = cls break if warnings and actually is None: - print(("WARNING: This chip doesn't appear to be a %s (chip magic value 0x%08x). " - "Probably it is unsupported by this version of esptool.") % (self.CHIP_NAME, chip_magic_value)) + print( + "WARNING: This chip doesn't appear to be a %s " + "(chip magic value 0x%08x). " + "Probably it is unsupported by this version of esptool." + % (self.CHIP_NAME, chip_magic_value) + ) else: - raise FatalError("This chip is %s not %s. Wrong --chip argument?" % (actually.CHIP_NAME, self.CHIP_NAME)) + raise FatalError( + "This chip is %s not %s. Wrong --chip argument?" + % (actually.CHIP_NAME, self.CHIP_NAME) + ) except UnsupportedCommandError: self.secure_download_mode = True self._post_connect() @@ -550,26 +664,33 @@ class ESPLoader(object): pass def read_reg(self, addr, timeout=DEFAULT_TIMEOUT): - """ Read memory address in target """ + """Read memory address in target""" # we don't call check_command here because read_reg() function is called - # when detecting chip type, and the way we check for success (STATUS_BYTES_LENGTH) is different - # for different chip types (!) - val, data = self.command(self.ESP_READ_REG, struct.pack(' 0: # add a dummy write to a date register as an excuse to have a delay - command += struct.pack(' start: - raise FatalError(("Software loader is resident at 0x%08x-0x%08x. " - "Can't load binary at overlapping address range 0x%08x-0x%08x. " - "Either change binary loading address, or use the --no-stub " - "option to disable the software loader.") % (start, end, load_start, load_end)) + raise FatalError( + "Software loader is resident at 0x%08x-0x%08x. " + "Can't load binary at overlapping address range 0x%08x-0x%08x. " + "Either change binary loading address, or use the --no-stub " + "option to disable the software loader." + % (start, end, load_start, load_end) + ) - return self.check_command("enter RAM download mode", self.ESP_MEM_BEGIN, - struct.pack(' length: - raise FatalError('Read more than expected') + raise FatalError("Read more than expected") digest_frame = self.read() if len(digest_frame) != 16: - raise FatalError('Expected digest, got: %s' % hexify(digest_frame)) + raise FatalError("Expected digest, got: %s" % hexify(digest_frame)) expected_digest = hexify(digest_frame).upper() digest = hashlib.md5(data).hexdigest().upper() if digest != expected_digest: - raise FatalError('Digest mismatch: expected %s, got %s' % (expected_digest, digest)) + raise FatalError( + "Digest mismatch: expected %s, got %s" % (expected_digest, digest) + ) return data def flash_spi_attach(self, hspi_arg): @@ -875,12 +1061,13 @@ class ESPLoader(object): has it as a SPI command. """ # last 3 bytes in ESP_SPI_ATTACH argument are reserved values - arg = struct.pack(' 0: - flags |= (dummy_len - 1) + flags |= dummy_len - 1 if addr_len > 0: flags |= (addr_len - 1) << SPI_USR_ADDR_LEN_SHIFT if flags: self.write_reg(SPI_USR1_REG, flags) + else: + def set_data_lengths(mosi_bits, miso_bits): SPI_DATA_LEN_REG = SPI_USR1_REG SPI_MOSI_BITLEN_S = 17 SPI_MISO_BITLEN_S = 8 mosi_mask = 0 if (mosi_bits == 0) else (mosi_bits - 1) miso_mask = 0 if (miso_bits == 0) else (miso_bits - 1) - flags = (miso_mask << SPI_MISO_BITLEN_S) | (mosi_mask << SPI_MOSI_BITLEN_S) + flags = (miso_mask << SPI_MISO_BITLEN_S) | ( + mosi_mask << SPI_MOSI_BITLEN_S + ) if dummy_len > 0: - flags |= (dummy_len - 1) + flags |= dummy_len - 1 if addr_len > 0: flags |= (addr_len - 1) << SPI_USR_ADDR_LEN_SHIFT self.write_reg(SPI_DATA_LEN_REG, flags) # SPI peripheral "command" bitmasks for SPI_CMD_REG - SPI_CMD_USR = (1 << 18) + SPI_CMD_USR = 1 << 18 # shift values SPI_USR2_COMMAND_LEN_SHIFT = 28 SPI_USR_ADDR_LEN_SHIFT = 26 if read_bits > 32: - raise FatalError("Reading more than 32 bits back from a SPI flash operation is unsupported") + raise FatalError( + "Reading more than 32 bits back from a SPI flash " + "operation is unsupported" + ) if len(data) > 64: - raise FatalError("Writing more than 64 bytes of data with one SPI command is unsupported") + raise FatalError( + "Writing more than 64 bytes of data with one SPI " + "command is unsupported" + ) data_bits = len(data) * 8 old_spi_usr = self.read_reg(SPI_USR_REG) @@ -988,14 +1205,15 @@ class ESPLoader(object): flags |= SPI_USR_DUMMY set_data_lengths(data_bits, read_bits) self.write_reg(SPI_USR_REG, flags) - self.write_reg(SPI_USR2_REG, - (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command) + self.write_reg( + SPI_USR2_REG, (7 << SPI_USR2_COMMAND_LEN_SHIFT) | spiflash_command + ) if addr and addr_len > 0: self.write_reg(SPI_ADDR_REG, addr) if data_bits == 0: self.write_reg(SPI_W0_REG, 0) # clear data register before we read it else: - data = pad_to(data, 4, b'\00') # pad to 32-bit multiple + data = pad_to(data, 4, b"\00") # pad to 32-bit multiple words = struct.unpack("I" * (len(data) // 4), data) next_reg = SPI_W0_REG for word in words: @@ -1008,6 +1226,7 @@ class ESPLoader(object): if (self.read_reg(SPI_CMD_REG) & SPI_CMD_USR) == 0: return raise FatalError("SPI command did not complete in time") + wait_done() status = self.read_reg(SPI_W0_REG) @@ -1018,7 +1237,9 @@ class ESPLoader(object): def read_spiflash_sfdp(self, addr, read_bits): CMD_RDSFDP = 0x5A - return self.run_spiflash_command(CMD_RDSFDP, read_bits=read_bits, addr=addr, addr_len=24, dummy_len=8) + return self.run_spiflash_command( + CMD_RDSFDP, read_bits=read_bits, addr=addr, addr_len=24, dummy_len=8 + ) def read_status(self, num_bytes=2): """Read up to 24 bits (num_bytes) of SPI flash status register contents @@ -1027,7 +1248,7 @@ class ESPLoader(object): Not all SPI flash supports all three commands. The upper 1 or 2 bytes may be 0xFF. """ - SPIFLASH_RDSR = 0x05 + SPIFLASH_RDSR = 0x05 SPIFLASH_RDSR2 = 0x35 SPIFLASH_RDSR3 = 0x15 @@ -1067,7 +1288,8 @@ class ESPLoader(object): self.run_spiflash_command(enable_cmd) self.run_spiflash_command(SPIFLASH_WRSR, struct.pack(" 33 else 26 if abs(norm_xtal - est_xtal) > 1: - print("WARNING: Detected crystal freq %.2fMHz is quite different to normalized freq %dMHz. Unsupported crystal in use?" % (est_xtal, norm_xtal)) + print( + "WARNING: Detected crystal freq %.2fMHz is quite different to " + "normalized freq %dMHz. Unsupported crystal in use?" + % (est_xtal, norm_xtal) + ) return norm_xtal def hard_reset(self): - print('Hard resetting via RTS pin...') + print("Hard resetting via RTS pin...") self._setRTS(True) # EN->LOW time.sleep(0.1) self._setRTS(False) @@ -1112,7 +1343,9 @@ class ESPLoader(object): self.flash_begin(0, 0) self.flash_finish(True) elif self.CHIP_NAME != "ESP8266": - raise FatalError("Soft resetting is currently only supported on ESP8266") + raise FatalError( + "Soft resetting is currently only supported on ESP8266" + ) else: # running user code from stub loader requires some hacks # in the stub loader @@ -1122,8 +1355,14 @@ class ESPLoader(object): try: chip_id = self.get_chip_id() if chip_id != self.IMAGE_CHIP_ID: - print("WARNING: Chip ID {} ({}) doesn't match expected Chip ID {}. esptool may not work correctly." - .format(chip_id, self.UNSUPPORTED_CHIPS.get(chip_id, 'Unknown'), self.IMAGE_CHIP_ID)) + print( + "WARNING: Chip ID {} ({}) doesn't match expected Chip ID {}. " + "esptool may not work correctly.".format( + chip_id, + self.UNSUPPORTED_CHIPS.get(chip_id, "Unknown"), + self.IMAGE_CHIP_ID, + ) + ) # Try to flash anyways by disabling stub self.stub_is_disabled = True except NotImplementedInROMError: @@ -1143,11 +1382,17 @@ def slip_reader(port, trace_function): while True: waiting = port.inWaiting() read_bytes = port.read(1 if waiting == 0 else waiting) - if read_bytes == b'': + if read_bytes == b"": if partial_packet is None: # fail due to no data - msg = "Serial data stream stopped: Possible serial noise or corruption." if successful_slip else "No serial data received." + msg = ( + "Serial data stream stopped: Possible serial noise or corruption." + if successful_slip + else "No serial data received." + ) else: # fail during packet transfer - msg = "Packet content transfer stopped (received {} bytes)".format(len(partial_packet)) + msg = "Packet content transfer stopped (received {} bytes)".format( + len(partial_packet) + ) trace_function(msg) raise FatalError(msg) trace_function("Read %d bytes: %s", len(read_bytes), HexFormatter(read_bytes)) @@ -1156,25 +1401,34 @@ def slip_reader(port, trace_function): b = bytes([b]) # python 2/3 compat if partial_packet is None: # waiting for packet header - if b == b'\xc0': + if b == b"\xc0": partial_packet = b"" else: trace_function("Read invalid data: %s", HexFormatter(read_bytes)) - trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) - raise FatalError('Invalid head of packet (0x%s): Possible serial noise or corruption.' % hexify(b)) + trace_function( + "Remaining data in serial buffer: %s", + HexFormatter(port.read(port.inWaiting())), + ) + raise FatalError( + "Invalid head of packet (0x%s): " + "Possible serial noise or corruption." % hexify(b) + ) elif in_escape: # part-way through escape sequence in_escape = False - if b == b'\xdc': - partial_packet += b'\xc0' - elif b == b'\xdd': - partial_packet += b'\xdb' + if b == b"\xdc": + partial_packet += b"\xc0" + elif b == b"\xdd": + partial_packet += b"\xdb" else: trace_function("Read invalid data: %s", HexFormatter(read_bytes)) - trace_function("Remaining data in serial buffer: %s", HexFormatter(port.read(port.inWaiting()))) - raise FatalError('Invalid SLIP escape (0xdb, 0x%s)' % (hexify(b))) - elif b == b'\xdb': # start of escape sequence + trace_function( + "Remaining data in serial buffer: %s", + HexFormatter(port.read(port.inWaiting())), + ) + raise FatalError("Invalid SLIP escape (0xdb, 0x%s)" % (hexify(b))) + elif b == b"\xdb": # start of escape sequence in_escape = True - elif b == b'\xc0': # end of packet + elif b == b"\xc0": # end of packet trace_function("Received full packet: %s", HexFormatter(partial_packet)) yield partial_packet partial_packet = None @@ -1199,6 +1453,7 @@ class HexFormatter(object): printed as separately indented lines, with ASCII decoding at the end of each line. """ + def __init__(self, binary_string, auto_split=True): self._s = binary_string self._auto_split = auto_split @@ -1209,10 +1464,21 @@ class HexFormatter(object): s = self._s while len(s) > 0: line = s[:16] - ascii_line = "".join(c if (c == ' ' or (c in string.printable and c not in string.whitespace)) - else '.' for c in line.decode('ascii', 'replace')) + ascii_line = "".join( + c + if ( + c == " " + or (c in string.printable and c not in string.whitespace) + ) + else "." + for c in line.decode("ascii", "replace") + ) s = s[16:] - result += "\n %-16s %-16s | %s" % (hexify(line[:8], False), hexify(line[8:], False), ascii_line) + result += "\n %-16s %-16s | %s" % ( + hexify(line[:8], False), + hexify(line[8:], False), + ascii_line, + ) return result else: return hexify(self._s, False) diff --git a/esptool/stub_flasher.py b/esptool/stub_flasher.py index b82aadb..64d8947 100644 --- a/esptool/stub_flasher.py +++ b/esptool/stub_flasher.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -7,6 +8,8 @@ from __future__ import division, print_function import base64 import zlib +# fmt: off + # Binary stub codes (see flasher_stub directory for source & details) ESP8266StubCode = eval(zlib.decompress(base64.b64decode(b""" eNq9Pftj1DbS/4rthCQbkiLZXq/Mo2w2yQItXCEcKddL28gvelxpwzZXcj34/vbP85Jl7yaB67U/LFl5ZWk0M5q3xH8265/OF//evB1oNUlNmmTjeCfYrOy5bZ8VmycXypxcGH1y0dT328aYP2n7Ue0nbj9J+5lw\ diff --git a/esptool/targets/esp32.py b/esptool/targets/esp32.py index f043d26..8eb1dc1 100644 --- a/esptool/targets/esp32.py +++ b/esptool/targets/esp32.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -97,7 +98,6 @@ class ESP32ROM(ESPLoader): """ Try to read the BLOCK1 (encryption key) and check if it is valid """ def is_flash_encryption_key_valid(self): - """Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1 this bit is at position 16 in EFUSE_BLK0_RDATA0_REG""" word0 = self.read_efuse(0) @@ -108,9 +108,10 @@ class ESP32ROM(ESPLoader): return True else: # reading of BLOCK1 is ALLOWED so we will read and verify for non-zero. - # When ESP32 has not generated AES/encryption key in BLOCK1, the contents will be readable and 0. - # If the flash encryption is enabled it is expected to have a valid non-zero key. We break out on - # first occurance of non-zero value + # When ESP32 has not generated AES/encryption key in BLOCK1, + # the contents will be readable and 0. + # If the flash encryption is enabled it is expected to have a valid + # non-zero key. We break out on first occurance of non-zero value key_word = [0] * 7 for i in range(len(key_word)): key_word[i] = self.read_efuse(14 + i) @@ -142,7 +143,10 @@ class ESP32ROM(ESPLoader): return 0xF def get_encrypted_download_disabled(self): - if self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG) & self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT: + if ( + self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG) + & self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT + ): return True else: return False @@ -277,7 +281,9 @@ class ESP32ROM(ESPLoader): RTC_CNTL_DREFH_SDIO_M = 3 << 29 RTC_CNTL_DREFM_SDIO_M = 3 << 27 RTC_CNTL_DREFL_SDIO_M = 3 << 25 - # RTC_CNTL_SDIO_TIEH = (1 << 23) # not used here, setting TIEH=1 would set 3.3V output, not safe for esptool.py to do + # RTC_CNTL_SDIO_TIEH = (1 << 23) + # not used here, setting TIEH=1 would set 3.3V output, + # not safe for esptool.py to do RTC_CNTL_SDIO_FORCE = 1 << 22 RTC_CNTL_SDIO_PD_EN = 1 << 21 @@ -308,9 +314,9 @@ class ESP32ROM(ESPLoader): "Expected %d byte block, got %d bytes. Serial errors?" % (block_len, len(r)) ) - data += r[ - :block_len - ] # command always returns 64 byte buffer, regardless of how many bytes were actually read from flash + # command always returns 64 byte buffer, + # regardless of how many bytes were actually read from flash + data += r[:block_len] if progress_fn and (len(data) % 1024 == 0 or len(data) == length): progress_fn(len(data), length) return data diff --git a/esptool/targets/esp32c2.py b/esptool/targets/esp32c2.py index 9adb9ed..ba66b95 100644 --- a/esptool/targets/esp32c2.py +++ b/esptool/targets/esp32c2.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp32c3.py b/esptool/targets/esp32c3.py index 243b1a5..6a6c4f0 100644 --- a/esptool/targets/esp32c3.py +++ b/esptool/targets/esp32c3.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp32c6beta.py b/esptool/targets/esp32c6beta.py index 562f721..4ff594c 100644 --- a/esptool/targets/esp32c6beta.py +++ b/esptool/targets/esp32c6beta.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp32h2beta1.py b/esptool/targets/esp32h2beta1.py index 56eb512..876674b 100644 --- a/esptool/targets/esp32h2beta1.py +++ b/esptool/targets/esp32h2beta1.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp32h2beta2.py b/esptool/targets/esp32h2beta2.py index a21f229..f39d5d5 100644 --- a/esptool/targets/esp32h2beta2.py +++ b/esptool/targets/esp32h2beta2.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp32s2.py b/esptool/targets/esp32s2.py index f663870..19cc9a2 100644 --- a/esptool/targets/esp32s2.py +++ b/esptool/targets/esp32s2.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -228,11 +229,15 @@ class ESP32S2ROM(ESP32ROM): """ if os.getenv("ESPTOOL_TESTING") is not None: print("ESPTOOL_TESTING is set, ignoring strapping mode check") - # Esptool tests over USB CDC run with GPIO0 strapped low, don't complain in this case. + # Esptool tests over USB CDC run with GPIO0 strapped low, + # don't complain in this case. return strap_reg = self.read_reg(self.GPIO_STRAP_REG) force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) - if strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0: + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): print( "WARNING: {} chip was placed into download mode using GPIO0.\n" "esptool.py can not exit the download mode over USB. " @@ -250,7 +255,8 @@ class ESP32S2ROM(ESP32ROM): print("Hard resetting via RTS pin...") self._setRTS(True) # EN->LOW if self.uses_usb(): - # Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions + # Give the chip some time to come out of reset, + # to be able to handle further DTR/RTS transitions time.sleep(0.2) self._setRTS(False) time.sleep(0.2) diff --git a/esptool/targets/esp32s3.py b/esptool/targets/esp32s3.py index 30a0f04..87ae366 100644 --- a/esptool/targets/esp32s3.py +++ b/esptool/targets/esp32s3.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -168,11 +169,15 @@ class ESP32S3ROM(ESP32ROM): """ if os.getenv("ESPTOOL_TESTING") is not None: print("ESPTOOL_TESTING is set, ignoring strapping mode check") - # Esptool tests over USB CDC run with GPIO0 strapped low, don't complain in this case. + # Esptool tests over USB CDC run with GPIO0 strapped low, + # don't complain in this case. return strap_reg = self.read_reg(self.GPIO_STRAP_REG) force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG) - if strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0: + if ( + strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0 + and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0 + ): print( "WARNING: {} chip was placed into download mode using GPIO0.\n" "esptool.py can not exit the download mode over USB. " @@ -190,7 +195,8 @@ class ESP32S3ROM(ESP32ROM): print("Hard resetting via RTS pin...") self._setRTS(True) # EN->LOW if self.uses_usb(): - # Give the chip some time to come out of reset, to be able to handle further DTR/RTS transitions + # Give the chip some time to come out of reset, + # to be able to handle further DTR/RTS transitions time.sleep(0.2) self._setRTS(False) time.sleep(0.2) diff --git a/esptool/targets/esp32s3beta2.py b/esptool/targets/esp32s3beta2.py index e782436..b4d90e3 100644 --- a/esptool/targets/esp32s3beta2.py +++ b/esptool/targets/esp32s3beta2.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later diff --git a/esptool/targets/esp8266.py b/esptool/targets/esp8266.py index ed96368..8b0bcac 100644 --- a/esptool/targets/esp8266.py +++ b/esptool/targets/esp8266.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -128,7 +129,9 @@ class ESP8266ROM(ESPLoader): super(ESP8266ROM, self).flash_set_parameters(size) def chip_id(self): - """Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() function""" + """ + Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() func + """ id0 = self.read_reg(self.ESP_OTP_MAC0) id1 = self.read_reg(self.ESP_OTP_MAC1) return (id0 >> 24) | ((id1 & 0xFFFFFF) << 8) diff --git a/esptool/util.py b/esptool/util.py index b10c7c9..8ba3401 100644 --- a/esptool/util.py +++ b/esptool/util.py @@ -1,6 +1,5 @@ -#!/usr/bin/env python -# -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -14,15 +13,18 @@ PYTHON2 = sys.version_info[0] < 3 # True if on pre-Python 3 # Function to return nth byte of a bitstring # Different behaviour on Python 2 vs 3 if PYTHON2: + def byte(bitstr, index): return ord(bitstr[index]) + else: + def byte(bitstr, index): return bitstr[index] def mask_to_shift(mask): - """ Return the index of the least significant bit in the mask """ + """Return the index of the least significant bit in the mask""" shift = 0 while mask & 0x1 == 0: shift += 1 @@ -31,16 +33,19 @@ def mask_to_shift(mask): def format_chip_name(c): - """ Normalize chip name from user input """ - c = c.lower().replace('-', '') - if c == 'esp8684': # TODO: Delete alias, ESPTOOL-389 - print('WARNING: Chip name ESP8684 is deprecated in favor of ESP32-C2 and will be removed in a future release. Using ESP32-C2 instead.') - return 'esp32c2' + """Normalize chip name from user input""" + c = c.lower().replace("-", "") + if c == "esp8684": # TODO: Delete alias, ESPTOOL-389 + print( + "WARNING: Chip name ESP8684 is deprecated in favor of ESP32-C2 " + "and will be removed in a future release. Using ESP32-C2 instead." + ) + return "esp32c2" return c def div_roundup(a, b): - """ Return a/b rounded up to nearest integer, + """Return a/b rounded up to nearest integer, equivalent result to int(math.ceil(float(int(a)) / float(int(b))), only without possible floating point accuracy errors. """ @@ -48,27 +53,27 @@ def div_roundup(a, b): def flash_size_bytes(size): - """ Given a flash size of the type passed in args.flash_size + """Given a flash size of the type passed in args.flash_size (ie 512KB or 1MB) then return the size in bytes. """ if "MB" in size: - return int(size[:size.index("MB")]) * 1024 * 1024 + return int(size[: size.index("MB")]) * 1024 * 1024 elif "KB" in size: - return int(size[:size.index("KB")]) * 1024 + return int(size[: size.index("KB")]) * 1024 else: raise FatalError("Unknown size %s" % size) def hexify(s, uppercase=True): - format_str = '%02X' if uppercase else '%02x' + format_str = "%02X" if uppercase else "%02x" if not PYTHON2: - return ''.join(format_str % c for c in s) + return "".join(format_str % c for c in s) else: - return ''.join(format_str % ord(c) for c in s) + return "".join(format_str % ord(c) for c in s) -def pad_to(data, alignment, pad_character=b'\xFF'): - """ Pad to the next alignment boundary """ +def pad_to(data, alignment, pad_character=b"\xFF"): + """Pad to the next alignment boundary""" pad_mod = len(data) % alignment if pad_mod != 0: data += pad_character * (alignment - pad_mod) @@ -76,16 +81,18 @@ def pad_to(data, alignment, pad_character=b'\xFF'): def print_overwrite(message, last_line=False): - """ Print a message, overwriting the currently printed line. + """Print a message, overwriting the currently printed line. - If last_line is False, don't append a newline at the end (expecting another subsequent call will overwrite this one.) + If last_line is False, don't append a newline at the end + (expecting another subsequent call will overwrite this one.) After a sequence of calls with last_line=False, call once with last_line=True. - If output is not a TTY (for example redirected a pipe), no overwriting happens and this function is the same as print(). + If output is not a TTY (for example redirected a pipe), + no overwriting happens and this function is the same as print(). """ if sys.stdout.isatty(): - print("\r%s" % message, end='\n' if last_line else '') + print("\r%s" % message, end="\n" if last_line else "") else: print(message) @@ -95,6 +102,7 @@ class FatalError(RuntimeError): Wrapper class for runtime errors that aren't caused by internal bugs, but by ESP ROM responses or input content. """ + def __init__(self, message): RuntimeError.__init__(self, message) @@ -106,21 +114,23 @@ class FatalError(RuntimeError): """ err_defs = { - 0x101: 'Out of memory', - 0x102: 'Invalid argument', - 0x103: 'Invalid state', - 0x104: 'Invalid size', - 0x105: 'Requested resource not found', - 0x106: 'Operation or feature not supported', - 0x107: 'Operation timed out', - 0x108: 'Received response was invalid', - 0x109: 'CRC or checksum was invalid', - 0x10A: 'Version was invalid', - 0x10B: 'MAC address was invalid', + 0x101: "Out of memory", + 0x102: "Invalid argument", + 0x103: "Invalid state", + 0x104: "Invalid size", + 0x105: "Requested resource not found", + 0x106: "Operation or feature not supported", + 0x107: "Operation timed out", + 0x108: "Received response was invalid", + 0x109: "CRC or checksum was invalid", + 0x10A: "Version was invalid", + 0x10B: "MAC address was invalid", } err_code = struct.unpack(">H", result[:2]) - message += " (result was {}: {})".format(hexify(result), err_defs.get(err_code[0], 'Unknown result')) + message += " (result was {}: {})".format( + hexify(result), err_defs.get(err_code[0], "Unknown result") + ) return FatalError(message) @@ -129,13 +139,21 @@ class NotImplementedInROMError(FatalError): Wrapper class for the error thrown when a particular ESP bootloader function is not implemented in the ROM bootloader. """ + def __init__(self, bootloader, func): - FatalError.__init__(self, "%s ROM does not support function %s." % (bootloader.CHIP_NAME, func.__name__)) + FatalError.__init__( + self, + "%s ROM does not support function %s." + % (bootloader.CHIP_NAME, func.__name__), + ) class NotSupportedError(FatalError): def __init__(self, esp, function_name): - FatalError.__init__(self, "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME)) + FatalError.__init__( + self, + "Function %s is not supported for %s." % (function_name, esp.CHIP_NAME), + ) class UnsupportedCommandError(RuntimeError): @@ -144,6 +162,7 @@ class UnsupportedCommandError(RuntimeError): Usually this indicates the loader is running in Secure Download Mode. """ + def __init__(self, esp, op): if esp.secure_download_mode: msg = "This command (0x%x) is not supported in Secure Download Mode" % op diff --git a/flasher_stub/compare_stubs.py b/flasher_stub/compare_stubs.py index 8017562..6f1e3fe 100755 --- a/flasher_stub/compare_stubs.py +++ b/flasher_stub/compare_stubs.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -21,13 +22,19 @@ def verbose_diff(new, old): for k in ["data", "text"]: if len(new[k]) != len(old[k]): - print("New %s %d bytes, old stub code %d bytes" % (k, len(new[k]), len(old[k]))) + print( + "New %s %d bytes, old stub code %d bytes" + % (k, len(new[k]), len(old[k])) + ) if new[k] != old[k]: print("%s is different" % k) if len(new[k]) == len(old[k]): for b in range(len(new[k])): if new[k][b] != old[k][b]: - print(" Byte 0x%x: new 0x%02x old 0x%02x" % (b, ord(new[k][b]), ord(old[k][b]))) + print( + " Byte 0x%x: new 0x%02x old 0x%02x" + % (b, ord(new[k][b]), ord(old[k][b])) + ) if __name__ == "__main__": @@ -35,8 +42,9 @@ if __name__ == "__main__": sys.path.append("..") import esptool import esptool.stub_flasher # old version in esptool module + sys.path.append("build") - import stub_flasher_snippet # new version in build directory + import stub_flasher_snippet # new version in build directory chip_list = [chip_name.upper() for chip_name in esptool.CHIP_LIST] @@ -46,7 +54,10 @@ if __name__ == "__main__": new = stub_flasher_snippet.__dict__[key] if old != new: - print("{} stub code in esptool.stub_flasher is different to just-built stub.".format(chip)) + print( + "{} stub code in esptool.stub_flasher is different " + "to just-built stub.".format(chip) + ) verbose_diff(new, old) same = False diff --git a/flasher_stub/esptool_test_stub.py b/flasher_stub/esptool_test_stub.py index 08082a0..12da464 100755 --- a/flasher_stub/esptool_test_stub.py +++ b/flasher_stub/esptool_test_stub.py @@ -24,12 +24,16 @@ import esptool # noqa: E402 # Python hackiness: evaluate the snippet in the context of the esptool module, so it # edits the esptool's global variables -exec(open("%s/build/stub_flasher_snippet.py" % THIS_DIR).read(), esptool.__dict__, esptool.__dict__) +exec( + open("%s/build/stub_flasher_snippet.py" % THIS_DIR).read(), + esptool.__dict__, + esptool.__dict__, +) if __name__ == "__main__": try: esptool.main() except esptool.FatalError as e: - print('\nA fatal error occurred: %s' % e) + print("\nA fatal error occurred: %s" % e) sys.exit(2) diff --git a/flasher_stub/wrap_stub.py b/flasher_stub/wrap_stub.py index 7a62c36..e147419 100755 --- a/flasher_stub/wrap_stub.py +++ b/flasher_stub/wrap_stub.py @@ -18,37 +18,44 @@ import re import sys import zlib -sys.path.append('..') +sys.path.append("..") import esptool # noqa: E402 def wrap_stub(elf_file): - """ Wrap an ELF file into a stub 'dict' """ - print('Wrapping ELF file %s...' % elf_file) + """Wrap an ELF file into a stub 'dict'""" + print("Wrapping ELF file %s..." % elf_file) e = esptool.bin_image.ELFFile(elf_file) - text_section = e.get_section('.text') + text_section = e.get_section(".text") try: - data_section = e.get_section('.data') + data_section = e.get_section(".data") except ValueError: data_section = None stub = { - 'text': text_section.data, - 'text_start': text_section.addr, - 'entry': e.entrypoint, + "text": text_section.data, + "text_start": text_section.addr, + "entry": e.entrypoint, } if data_section is not None: - stub['data'] = data_section.data - stub['data_start'] = data_section.addr + stub["data"] = data_section.data + stub["data_start"] = data_section.addr # Pad text with NOPs to mod 4. - if len(stub['text']) % 4 != 0: - stub['text'] += (4 - (len(stub['text']) % 4)) * '\0' + if len(stub["text"]) % 4 != 0: + stub["text"] += (4 - (len(stub["text"]) % 4)) * "\0" - print('Stub text: %d @ 0x%08x, data: %d @ 0x%08x, entry @ 0x%x' % ( - len(stub['text']), stub['text_start'], - len(stub.get('data', '')), stub.get('data_start', 0), - stub['entry']), file=sys.stderr) + print( + "Stub text: %d @ 0x%08x, data: %d @ 0x%08x, entry @ 0x%x" + % ( + len(stub["text"]), + stub["text_start"], + len(stub.get("data", "")), + stub.get("data_start", 0), + stub["entry"], + ), + file=sys.stderr, + ) return stub @@ -68,12 +75,14 @@ STUB_FLASHER_PY = "../esptool/stub_flasher.py" def write_python_snippet_to_file(stub_name, stub_data, out_file): print("writing %s stub" % stub_name) - encoded = base64.b64encode(zlib.compress(repr(stub_data).encode("utf-8"), 9)).decode("utf-8") + encoded = base64.b64encode( + zlib.compress(repr(stub_data).encode("utf-8"), 9) + ).decode("utf-8") in_lines = "" # split encoded data into 160 character lines LINE_LEN = 160 for c in range(0, len(encoded), LINE_LEN): - in_lines += encoded[c:c + LINE_LEN] + "\\\n" + in_lines += encoded[c : c + LINE_LEN] + "\\\n" out_file.write(PYTHON_TEMPLATE % (stub_name, in_lines)) @@ -86,7 +95,7 @@ def write_python_snippets(stub_dict, out_file): def embed_python_snippets(stubs): - with open(STUB_FLASHER_PY, 'r') as f: + with open(STUB_FLASHER_PY, "r") as f: lines = [line for line in f] with open(STUB_FLASHER_PY, "w") as f: @@ -113,21 +122,27 @@ def embed_python_snippets(stubs): def stub_name(filename): - """ Return a dictionary key for the stub with filename 'filename' """ + """Return a dictionary key for the stub with filename 'filename'""" return os.path.splitext(os.path.basename(filename))[0] -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("--out-file", required=False, type=argparse.FileType('w'), - help="Output file name. If not specified, stubs are embedded into esptool.py.") + parser.add_argument( + "--out-file", + required=False, + type=argparse.FileType("w"), + help="Output file name. If not specified, stubs are embedded into esptool.py.", + ) parser.add_argument("elf_files", nargs="+", help="Stub ELF files to convert") args = parser.parse_args() - stubs = dict((stub_name(elf_file), wrap_stub(elf_file)) for elf_file in args.elf_files) + stubs = dict( + (stub_name(elf_file), wrap_stub(elf_file)) for elf_file in args.elf_files + ) if args.out_file: - print('Dumping to Python snippet file %s.' % args.out_file.name) + print("Dumping to Python snippet file %s." % args.out_file.name) write_python_snippets(stubs, args.out_file) else: - print('Embedding Python snippets into esptool.py') + print("Embedding Python snippets into esptool.py") embed_python_snippets(stubs) diff --git a/setup.cfg b/setup.cfg index 317b2fa..d92dd9d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,5 @@ [flake8] -exclude = ecdsa,.git,__pycache__,.eggs,build -max-line-length = 160 +exclude = .git,__pycache__,.eggs,build ignore = # multiple spaces before operator - used for visual indent of constants in some files E221, @@ -17,18 +16,25 @@ per-file-ignores = # tests often manipulate sys.path before importing the main tools, so ignore import order violations test/*.py: E402, - # multiple spaces after ',' - used for visual layout of eFuse data - espefuse/efuse/*/mem_definition.py: E241, - espefuse/efuse/*/fields.py: E241, + # multiple spaces after ',' and long lines - used for visual layout of eFuse data + espefuse/efuse/*/mem_definition.py: E241, E501, + espefuse/efuse/*/operations.py: E241, E501, F401, + espefuse/efuse/*/fields.py: E241, E501, + + # ignore long lines - used for RS encoding pairs + test/test_modules.py: E501, # don't require future imports or check for unused imports in __init__.py files - __init__.py: FI10, FI13, F401 + __init__.py: FI10, FI13, F401, # don't require future imports - __main__.py: FI10, FI13 - esptool.py: FI10, FI13 - espefuse.py: FI10, FI13 - espsecure.py: FI10, FI13 - test/sitecustomize.py: FI10, FI13 - docs/conf_common.py: FI10, FI13, F405 - docs/en/conf.py: FI10, FI13 + __main__.py: FI10, FI13, + esptool.py: FI10, FI13, + espefuse.py: FI10, FI13, + espsecure.py: FI10, FI13, + test/sitecustomize.py: FI10, FI13, + docs/conf_common.py: FI10, FI13, F405, + docs/en/conf.py: FI10, FI13, +# Compatibility with Black +max-line-length = 88 +extend-ignore = E203, W503, diff --git a/setup.py b/setup.py index 891b734..419a114 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ -# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, Espressif Systems (Shanghai) CO LTD, other contributors as noted. +# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton, +# Espressif Systems (Shanghai) CO LTD, other contributors as noted. # # SPDX-License-Identifier: GPL-2.0-or-later @@ -12,8 +13,11 @@ import sys try: from setuptools import find_packages, setup except ImportError: - print('Package setuptools is missing from your Python installation. Please see the installation section ' - 'in the esptool documentation for instructions on how to install it.') + print( + "Package setuptools is missing from your Python installation. " + "Please see the installation section in the esptool documentation" + " for instructions on how to install it." + ) exit(1) @@ -21,16 +25,15 @@ except ImportError: # https://packaging.python.org/en/latest/guides/single-sourcing-package-version/ def read(*names, **kwargs): with io.open( - os.path.join(os.path.dirname(__file__), *names), - encoding=kwargs.get("encoding", "utf8") + os.path.join(os.path.dirname(__file__), *names), + encoding=kwargs.get("encoding", "utf8"), ) as fp: return fp.read() def find_version(*file_paths): version_file = read(*file_paths) - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") @@ -40,80 +43,87 @@ long_description = """ ========== esptool.py ========== -A Python-based, open-source, platform-independent utility to communicate with the ROM bootloader in Espressif chips. +A Python-based, open-source, platform-independent utility to communicate with \ +the ROM bootloader in Espressif chips. The esptool.py project is `hosted on github `_. Documentation ------------- - -Visit online `esptool documentation `_ or run ``esptool -h``. +Visit online `esptool documentation `_ \ +or run ``esptool -h``. Contributing ------------ -Please see the `contributions guide `_. +Please see the `contributions guide \ +`_. """ setup( - name='esptool', - version=find_version('esptool/__init__.py'), - description='A serial utility to communicate & flash code to Espressif chips.', + name="esptool", + version=find_version("esptool/__init__.py"), + description="A serial utility to communicate & flash code to Espressif chips.", long_description=long_description, - url='https://github.com/espressif/esptool/', + url="https://github.com/espressif/esptool/", project_urls={ - 'Documentation': 'https://docs.espressif.com/projects/esptool/', - 'Source': 'https://github.com/espressif/esptool/', - 'Tracker': 'https://github.com/espressif/esptool/issues/', + "Documentation": "https://docs.espressif.com/projects/esptool/", + "Source": "https://github.com/espressif/esptool/", + "Tracker": "https://github.com/espressif/esptool/issues/", }, - author='Fredrik Ahlberg (themadinventor) & Angus Gratton (projectgus) & Espressif Systems', - author_email='', - license='GPLv2+', + author="Fredrik Ahlberg (themadinventor) & Angus Gratton (projectgus) " + "& Espressif Systems", + author_email="", + license="GPLv2+", classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'Operating System :: POSIX', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: MacOS :: MacOS X', - 'Topic :: Software Development :: Embedded Systems', - 'Environment :: Console', - 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', # Note: when dropping 3.4 support we can also remove the check in setup_requires - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX", + "Operating System :: Microsoft :: Windows", + "Operating System :: MacOS :: MacOS X", + "Topic :: Software Development :: Embedded Systems", + "Environment :: Console", + "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", + "Programming Language :: Python :: 2.7", + # Note: when dropping 3.4, 3.5 support, remove the check in setup_requires + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], setup_requires=( - ['wheel'] - if 'bdist_wheel' in sys.argv and sys.version_info[0:2] not in [(3, 4), (3, 5)] else - [] + ["wheel"] + if "bdist_wheel" in sys.argv and sys.version_info[0:2] not in [(3, 4), (3, 5)] + else [] ), extras_require={ "dev": [ - 'flake8>=3.2.0', - 'flake8-future-import', - 'flake8-import-order', - 'pyelftools', - 'unittest-xml-reporting<=2.5.2', # the replacement of the old xmlrunner package (Python 2 comp. version) - 'coverage', + "flake8>=3.2.0", + "flake8-future-import", + "flake8-import-order", + "pyelftools", + # the replacement of the old xmlrunner package (Python 2 comp. version) + "unittest-xml-reporting<=2.5.2", + "coverage", + "black", + "pre-commit", ], }, install_requires=[ - 'bitstring>=3.1.6', - 'cryptography>=2.1.4', - 'ecdsa>=0.16.0', - 'pyserial>=3.0', - 'reedsolo>=1.5.3,<=1.5.4', + "bitstring>=3.1.6", + "cryptography>=2.1.4", + "ecdsa>=0.16.0", + "pyserial>=3.0", + "reedsolo>=1.5.3,<=1.5.4", ], packages=find_packages(), entry_points={ - 'console_scripts': [ - 'esptool=esptool.__init__:_main', - 'espsecure=espsecure.__init__:_main', - 'espefuse=espefuse.__init__:_main', + "console_scripts": [ + "esptool=esptool.__init__:_main", + "espsecure=espsecure.__init__:_main", + "espefuse=espefuse.__init__:_main", ], - } + }, ) diff --git a/test/efuse_scripts/efuse_burn1.py b/test/efuse_scripts/efuse_burn1.py index 0692619..ea337ed 100644 --- a/test/efuse_scripts/efuse_burn1.py +++ b/test/efuse_scripts/efuse_burn1.py @@ -8,11 +8,11 @@ import json config = json.load(args.configfiles[0]) -assert args.index == 10, 'Index should be 10' +assert args.index == 10, "Index should be 10" -for cmd in config['burn_efuses1']: +for cmd in config["burn_efuses1"]: cmd = cmd.format(index=args.index) print(cmd) espefuse(esp, efuses, args, cmd) -assert args.index == 10, 'Index should be 10' +assert args.index == 10, "Index should be 10" diff --git a/test/efuse_scripts/efuse_burn2.py b/test/efuse_scripts/efuse_burn2.py index 85d6572..559f2cd 100644 --- a/test/efuse_scripts/efuse_burn2.py +++ b/test/efuse_scripts/efuse_burn2.py @@ -8,11 +8,11 @@ import json config = json.load(args.configfiles[0]) -assert args.index == 28, 'Should be index from the first script = 28' +assert args.index == 28, "Should be index from the first script = 28" -for cmd in config['burn_efuses2']: +for cmd in config["burn_efuses2"]: cmd = cmd.format(index=args.index) print(cmd) espefuse(esp, efuses, args, cmd) -assert args.index == 28, 'Should be index from the first script = 28' +assert args.index == 28, "Should be index from the first script = 28" diff --git a/test/efuse_scripts/esp32/test_efuse_script.py b/test/efuse_scripts/esp32/test_efuse_script.py index 26399cf..becedd0 100644 --- a/test/efuse_scripts/esp32/test_efuse_script.py +++ b/test/efuse_scripts/esp32/test_efuse_script.py @@ -1,4 +1,5 @@ # flake8: noqa +# fmt: off espefuse(esp, efuses, args, "burn_efuse JTAG_DISABLE 1 DISABLE_SDIO_HOST 1 CONSOLE_DEBUG_DISABLE 1") espefuse(esp, efuses, args, "burn_key flash_encryption ../../images/efuse/256bit --no-protect-key") espefuse(esp, efuses, args, "burn_key_digest ../../secure_images/rsa_secure_boot_signing_key.pem") diff --git a/test/efuse_scripts/esp32/test_efuse_script2.py b/test/efuse_scripts/esp32/test_efuse_script2.py index 78c220a..e906f61 100644 --- a/test/efuse_scripts/esp32/test_efuse_script2.py +++ b/test/efuse_scripts/esp32/test_efuse_script2.py @@ -1,4 +1,5 @@ # flake8: noqa +# fmt: off espefuse(esp, efuses, args, "burn_efuse JTAG_DISABLE 1 DISABLE_SDIO_HOST 1 CONSOLE_DEBUG_DISABLE 1") if efuses["JTAG_DISABLE"].get() != 0: raise esptool.FatalError("Burn should be at the end") diff --git a/test/efuse_scripts/esp32xx/test_efuse_script.py b/test/efuse_scripts/esp32xx/test_efuse_script.py index 617c807..d40c04b 100644 --- a/test/efuse_scripts/esp32xx/test_efuse_script.py +++ b/test/efuse_scripts/esp32xx/test_efuse_script.py @@ -1,4 +1,5 @@ # flake8: noqa +# fmt: off espefuse(esp, efuses, args, 'burn_efuse DIS_FORCE_DOWNLOAD 1 DIS_CAN 1 DIS_USB_DOWNLOAD_MODE 1') espefuse(esp, efuses, args, 'burn_bit BLOCK_USR_DATA 64 66 69 72 78 82 83 90') espefuse(esp, efuses, args, 'read_protect_efuse BLOCK_SYS_DATA2') diff --git a/test/efuse_scripts/esp32xx/test_efuse_script2.py b/test/efuse_scripts/esp32xx/test_efuse_script2.py index 8dd488b..b0a4416 100644 --- a/test/efuse_scripts/esp32xx/test_efuse_script2.py +++ b/test/efuse_scripts/esp32xx/test_efuse_script2.py @@ -1,4 +1,5 @@ # flake8: noqa +# fmt: off espefuse(esp, efuses, args, 'burn_efuse DIS_FORCE_DOWNLOAD 1 DIS_CAN 1 DIS_USB_DOWNLOAD_MODE 1') if efuses["DIS_FORCE_DOWNLOAD"].get() != 0: raise esptool.FatalError("Burn should be at the end") diff --git a/test/sitecustomize.py b/test/sitecustomize.py index 85ff036..b9ae507 100644 --- a/test/sitecustomize.py +++ b/test/sitecustomize.py @@ -1,4 +1,5 @@ import coverage + coverage.process_startup() # This file exists to perform arbitrary site-specific customizations. diff --git a/test/test_espefuse.py b/test/test_espefuse.py index 5420102..20988c5 100755 --- a/test/test_espefuse.py +++ b/test/test_espefuse.py @@ -20,7 +20,7 @@ import time import unittest from io import StringIO -sys.path.append('..') +sys.path.append("..") import espefuse import esptool @@ -43,7 +43,6 @@ class EspEfuseArgs(object): class EfuseTestCase(unittest.TestCase): - def setUp(self): # reset and zero efuses serialport.dtr = False @@ -56,7 +55,9 @@ class EfuseTestCase(unittest.TestCase): # connect & verify efuses are really zero self.esp = espefuse.get_esp(serialport, 115200, "default_reset") # dict mapping register name to its efuse object - self.efuses, self.operations = espefuse.get_efuses(esp=self.esp, do_not_confirm=True) + self.efuses, self.operations = espefuse.get_efuses( + esp=self.esp, do_not_confirm=True + ) if type(self.esp) is esptool.ESP32ROM: self.BLK1 = "BLOCK1" self.BLK2 = "BLOCK2" @@ -72,17 +73,22 @@ class EfuseTestCase(unittest.TestCase): if efuse.name == "CLK8M_FREQ": continue val = efuse.get_raw() - BAD_EFUSE_MSG = ("Efuse %s not all zeroes - either this is a real ESP32 chip (VERY BAD, read top of file), " - "or the reset is not erasing all efuses correctly.") % efuse.name + BAD_EFUSE_MSG = ( + "Efuse %s not all zeroes - either this is a real ESP32 chip " + "(VERY BAD, read top of file), " + "or the reset is not erasing all efuses correctly." + ) % efuse.name try: - self.assertEqual(b'\x00' * len(val), val, BAD_EFUSE_MSG) + self.assertEqual(b"\x00" * len(val), val, BAD_EFUSE_MSG) except TypeError: self.assertEqual(0, val, BAD_EFUSE_MSG) def _set_34_coding_scheme(self): self.efuses["CODING_SCHEME"].burn(1) # EspEfuses constructor needs to re-load CODING_SCHEME - self.efuses, self.operations = espefuse.get_efuses(esp=self.esp, do_not_confirm=True) + self.efuses, self.operations = espefuse.get_efuses( + esp=self.esp, do_not_confirm=True + ) class TestBurnKey(EfuseTestCase): @@ -177,7 +183,6 @@ class TestBurnKey(EfuseTestCase): class TestBurnBlockData(EfuseTestCase): - def test_burn_block_data_normal(self): word_a = 0x1234 word_b = 0x789A @@ -189,7 +194,9 @@ class TestBurnBlockData(EfuseTestCase): args.offset = 4 self.operations.burn_block_data(self.esp, self.efuses, args) - words = self.efuses.blocks[self.efuses.get_index_block_by_name(self.BLK1)].get_words() + words = self.efuses.blocks[ + self.efuses.get_index_block_by_name(self.BLK1) + ].get_words() self.assertEqual([0, word_a, word_b, 0, 0, 0, 0, 0], words) args.block = [self.BLK1] @@ -198,7 +205,9 @@ class TestBurnBlockData(EfuseTestCase): args.force_write_always = True self.operations.burn_block_data(self.esp, self.efuses, args) - words = self.efuses.blocks[self.efuses.get_index_block_by_name(self.BLK1)].get_words() + words = self.efuses.blocks[ + self.efuses.get_index_block_by_name(self.BLK1) + ].get_words() self.assertEqual([0, word_a, word_b, 0, 0, 0, word_a, word_b], words) self.assertEqual(0, self.efuses.get_coding_scheme_warnings()) @@ -215,25 +224,39 @@ class TestBurnBlockData(EfuseTestCase): args.offset = 6 self.operations.burn_block_data(self.esp, self.efuses, args) - words = self.efuses.blocks[self.efuses.get_index_block_by_name(self.BLK3)].get_words() - self.assertEqual([0, - struct.unpack(" [optional tests]" % sys.argv[0]) sys.exit(1) if sys.argv[1] != "--i-use-fpga": - raise esptool.FatalError("You need to use --i-use-fpga to confirm these tests are not being run on a real ESP chip!") + raise esptool.FatalError( + "You need to use --i-use-fpga to confirm these tests " + "are not being run on a real ESP chip!" + ) serialport = serial.Serial(sys.argv[2], 115200) serialport.dtr = False serialport.rts = False diff --git a/test/test_espefuse_host.py b/test/test_espefuse_host.py index 71a13ae..83189d3 100755 --- a/test/test_espefuse_host.py +++ b/test/test_espefuse_host.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -# HOST_TEST for espefuse.py [support esp32, esp32s2, esp32s3beta2, esp32s3, esp32c3, esp32h2beta1, esp32c2] +# HOST_TEST for espefuse.py +# [support esp32, esp32s2, esp32s3beta2, esp32s3, esp32c3, esp32h2beta1, esp32c2] # # How to use it: # @@ -34,12 +35,20 @@ from bitstring import BitString import serial TEST_DIR = os.path.abspath(os.path.dirname(__file__)) -ESPEFUSE_PY = os.path.abspath(os.path.join(TEST_DIR, '..', 'espefuse/__init__.py')) -ESPEFUSE_DIR = os.path.abspath(os.path.join(TEST_DIR, '..')) +ESPEFUSE_PY = os.path.abspath(os.path.join(TEST_DIR, "..", "espefuse/__init__.py")) +ESPEFUSE_DIR = os.path.abspath(os.path.join(TEST_DIR, "..")) os.chdir(TEST_DIR) sys.path.insert(0, os.path.join(TEST_DIR, "..")) -support_list_chips = ["esp32", "esp32s2", "esp32s3beta2", "esp32s3", "esp32c3", "esp32h2beta1", "esp32c2"] +support_list_chips = [ + "esp32", + "esp32s2", + "esp32s3beta2", + "esp32s3", + "esp32c3", + "esp32h2beta1", + "esp32c2", +] try: chip_target = sys.argv[1] @@ -53,13 +62,18 @@ espefuse_port = None class EfuseTestCase(unittest.TestCase): - def setUp(self): if reset_port is None: self.efuse_file = tempfile.NamedTemporaryFile() - self.base_cmd = "python {} --chip {} --virt --path-efuse-file {} -d ".format(ESPEFUSE_PY, chip_target, self.efuse_file.name) + self.base_cmd = ( + "python {} --chip {} --virt --path-efuse-file {} -d ".format( + ESPEFUSE_PY, chip_target, self.efuse_file.name + ) + ) else: - self.base_cmd = "python {} --chip {} -p {} -d ".format(ESPEFUSE_PY, chip_target, espefuse_port) + self.base_cmd = "python {} --chip {} -p {} -d ".format( + ESPEFUSE_PY, chip_target, espefuse_port + ) self.reset_efuses() def tearDown(self): @@ -80,10 +94,12 @@ class EfuseTestCase(unittest.TestCase): def get_esptool(self): if espefuse_port is not None: import esptool + esp = esptool.cmds.detect_chip(port=espefuse_port) del esptool else: import espefuse + efuse = espefuse.SUPPORTED_CHIPS[chip_target].efuse_lib esp = efuse.EmulateEfuseController(self.efuse_file.name) del espefuse @@ -91,29 +107,41 @@ class EfuseTestCase(unittest.TestCase): return esp def _set_34_coding_scheme(self): - self.espefuse_py('burn_efuse CODING_SCHEME 1') + self.espefuse_py("burn_efuse CODING_SCHEME 1") - def check_data_block_in_log(self, log, file_path, repeat=1, reverse_order=False, offset=0): - with open(file_path, 'rb') as f: - data = BitString('0x00') * offset + BitString(f) + def check_data_block_in_log( + self, log, file_path, repeat=1, reverse_order=False, offset=0 + ): + with open(file_path, "rb") as f: + data = BitString("0x00") * offset + BitString(f) blk = data.readlist("%d*uint:8" % (data.len // 8)) blk = blk[::-1] if reverse_order else blk hex_blk = " ".join("{:02x}".format(num) for num in blk) self.assertEqual(repeat, log.count(hex_blk)) def espefuse_not_virt_py(self, cmd, check_msg=None, ret_code=0): - full_cmd = ' '.join(('python {}'.format(ESPEFUSE_PY), cmd)) + full_cmd = " ".join(("python {}".format(ESPEFUSE_PY), cmd)) return self._run_command(full_cmd, check_msg, ret_code) def espefuse_py(self, cmd, do_not_confirm=True, check_msg=None, ret_code=0): - full_cmd = ' '.join([self.base_cmd, '--do-not-confirm' if do_not_confirm else '', cmd]) + full_cmd = " ".join( + [self.base_cmd, "--do-not-confirm" if do_not_confirm else "", cmd] + ) output = self._run_command(full_cmd, check_msg, ret_code) - self._run_command(' '.join([self.base_cmd, 'check_error']), 'No errors detected', 0) + self._run_command( + " ".join([self.base_cmd, "check_error"]), "No errors detected", 0 + ) return output def _run_command(self, cmd, check_msg, ret_code): try: - p = subprocess.Popen(cmd.split(), shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True) + p = subprocess.Popen( + cmd.split(), + shell=False, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + universal_newlines=True, + ) output, _ = p.communicate() returncode = p.returncode if check_msg: @@ -128,13 +156,12 @@ class EfuseTestCase(unittest.TestCase): class TestReadCommands(EfuseTestCase): - def test_help(self): - self.espefuse_not_virt_py("--help", check_msg='usage: __init__.py [-h]') + self.espefuse_not_virt_py("--help", check_msg="usage: __init__.py [-h]") self.espefuse_not_virt_py("--chip %s --help" % (chip_target)) def test_help2(self): - self.espefuse_not_virt_py('', check_msg='usage: __init__.py [-h]', ret_code=1) + self.espefuse_not_virt_py("", check_msg="usage: __init__.py [-h]", ret_code=1) def test_dump(self): self.espefuse_py("dump -h") @@ -150,11 +177,11 @@ class TestReadCommands(EfuseTestCase): def test_get_custom_mac(self): self.espefuse_py("get_custom_mac -h") if chip_target == "esp32": - right_msg = 'Custom MAC Address is not set in the device.' + right_msg = "Custom MAC Address is not set in the device." elif chip_target == "esp32h2beta1": - right_msg = 'Custom MAC Address: 00:00:00:00:00:00:00:00 (OK)' + right_msg = "Custom MAC Address: 00:00:00:00:00:00:00:00 (OK)" else: - right_msg = 'Custom MAC Address: 00:00:00:00:00:00 (OK)' + right_msg = "Custom MAC Address: 00:00:00:00:00:00 (OK)" self.espefuse_py("get_custom_mac", check_msg=right_msg) def test_adc_info(self): @@ -171,311 +198,504 @@ class TestReadProtectionCommands(EfuseTestCase): def test_read_protect_efuse(self): self.espefuse_py("read_protect_efuse -h") if chip_target == "esp32": - cmd = 'read_protect_efuse \ + cmd = "read_protect_efuse \ CODING_SCHEME \ MAC_VERSION \ BLOCK1 \ BLOCK2 \ - BLOCK3' + BLOCK3" count_protects = 5 elif chip_target == "esp32c2": - cmd = 'read_protect_efuse \ - BLOCK_KEY0_LOW_128' + cmd = "read_protect_efuse \ + BLOCK_KEY0_LOW_128" count_protects = 1 else: - self.espefuse_py('burn_efuse \ - KEY_PURPOSE_0 XTS_AES_256_KEY_1 \ - KEY_PURPOSE_1 XTS_AES_256_KEY_2 \ - KEY_PURPOSE_2 XTS_AES_128_KEY \ - KEY_PURPOSE_3 HMAC_DOWN_ALL \ - KEY_PURPOSE_4 HMAC_DOWN_JTAG \ - KEY_PURPOSE_5 HMAC_DOWN_DIGITAL_SIGNATURE') - cmd = 'read_protect_efuse \ + self.espefuse_py( + "burn_efuse \ + KEY_PURPOSE_0 XTS_AES_256_KEY_1 \ + KEY_PURPOSE_1 XTS_AES_256_KEY_2 \ + KEY_PURPOSE_2 XTS_AES_128_KEY \ + KEY_PURPOSE_3 HMAC_DOWN_ALL \ + KEY_PURPOSE_4 HMAC_DOWN_JTAG \ + KEY_PURPOSE_5 HMAC_DOWN_DIGITAL_SIGNATURE" + ) + cmd = "read_protect_efuse \ BLOCK_KEY0 \ BLOCK_KEY1 \ BLOCK_KEY2 \ BLOCK_KEY3 \ BLOCK_KEY4 \ - BLOCK_KEY5' + BLOCK_KEY5" count_protects = 6 self.espefuse_py(cmd) output = self.espefuse_py(cmd) self.assertEqual(count_protects, output.count("is already read protected")) def test_read_protect_efuse2(self): - self.espefuse_py('write_protect_efuse RD_DIS') + self.espefuse_py("write_protect_efuse RD_DIS") if chip_target == "esp32": - efuse_name = 'CODING_SCHEME' + efuse_name = "CODING_SCHEME" elif chip_target == "esp32c2": - efuse_name = 'BLOCK_KEY0_HI_128' + efuse_name = "BLOCK_KEY0_HI_128" else: - efuse_name = 'BLOCK_SYS_DATA2' - self.espefuse_py('read_protect_efuse {}'.format(efuse_name), - check_msg='A fatal error occurred: This efuse cannot be read-disabled due the to RD_DIS field is already write-disabled', - ret_code=2) + efuse_name = "BLOCK_SYS_DATA2" + self.espefuse_py( + "read_protect_efuse {}".format(efuse_name), + check_msg="A fatal error occurred: This efuse cannot be read-disabled " + "due the to RD_DIS field is already write-disabled", + ret_code=2, + ) @unittest.skipUnless(chip_target == "esp32", "when the purpose of BLOCK2 is set") def test_read_protect_efuse3(self): - self.espefuse_py('burn_efuse ABS_DONE_1 1') - self.espefuse_py('burn_key BLOCK2 images/efuse/256bit') - self.espefuse_py('read_protect_efuse BLOCK2', - check_msg='Secure Boot V2 is on (ABS_DONE_1 = True), BLOCK2 must be readable, stop this operation!', - ret_code=2) + self.espefuse_py("burn_efuse ABS_DONE_1 1") + self.espefuse_py("burn_key BLOCK2 images/efuse/256bit") + self.espefuse_py( + "read_protect_efuse BLOCK2", + check_msg="Secure Boot V2 is on (ABS_DONE_1 = True), " + "BLOCK2 must be readable, stop this operation!", + ret_code=2, + ) def test_read_protect_efuse4(self): if chip_target == "esp32": - self.espefuse_py('burn_key BLOCK2 images/efuse/256bit') - msg = 'must be readable, please stop this operation!' - self.espefuse_py('read_protect_efuse BLOCK2', check_msg=msg) + self.espefuse_py("burn_key BLOCK2 images/efuse/256bit") + msg = "must be readable, please stop this operation!" + self.espefuse_py("read_protect_efuse BLOCK2", check_msg=msg) elif chip_target == "esp32c2": - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST') - self.espefuse_py('read_protect_efuse BLOCK_KEY0', - check_msg='A fatal error occurred: BLOCK_KEY0 must be readable, stop this operation!', ret_code=2) + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST" + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY0", + check_msg="A fatal error occurred: " + "BLOCK_KEY0 must be readable, stop this operation!", + ret_code=2, + ) else: - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/256bit USER \ - BLOCK_KEY1 images/efuse/256bit RESERVED \ - BLOCK_KEY2 images/efuse/256bit SECURE_BOOT_DIGEST0 \ - BLOCK_KEY3 images/efuse/256bit SECURE_BOOT_DIGEST1 \ - BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST2 \ - BLOCK_KEY5 images/efuse/256bit HMAC_UP') - self.espefuse_py('read_protect_efuse BLOCK_KEY0', - check_msg='A fatal error occurred: BLOCK_KEY0 must be readable, stop this operation!', ret_code=2) - self.espefuse_py('read_protect_efuse BLOCK_KEY1', - check_msg='A fatal error occurred: BLOCK_KEY1 must be readable, stop this operation!', ret_code=2) - self.espefuse_py('read_protect_efuse BLOCK_KEY2', - check_msg='A fatal error occurred: BLOCK_KEY2 must be readable, stop this operation!', ret_code=2) - self.espefuse_py('read_protect_efuse BLOCK_KEY3', - check_msg='A fatal error occurred: BLOCK_KEY3 must be readable, stop this operation!', ret_code=2) - self.espefuse_py('read_protect_efuse BLOCK_KEY4', - check_msg='A fatal error occurred: BLOCK_KEY4 must be readable, stop this operation!', ret_code=2) - self.espefuse_py('read_protect_efuse BLOCK_KEY5') + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/256bit USER \ + BLOCK_KEY1 images/efuse/256bit RESERVED \ + BLOCK_KEY2 images/efuse/256bit SECURE_BOOT_DIGEST0 \ + BLOCK_KEY3 images/efuse/256bit SECURE_BOOT_DIGEST1 \ + BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST2 \ + BLOCK_KEY5 images/efuse/256bit HMAC_UP" + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY0", + check_msg="A fatal error occurred: " + "BLOCK_KEY0 must be readable, stop this operation!", + ret_code=2, + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY1", + check_msg="A fatal error occurred: " + "BLOCK_KEY1 must be readable, stop this operation!", + ret_code=2, + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY2", + check_msg="A fatal error occurred: " + "BLOCK_KEY2 must be readable, stop this operation!", + ret_code=2, + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY3", + check_msg="A fatal error occurred: " + "BLOCK_KEY3 must be readable, stop this operation!", + ret_code=2, + ) + self.espefuse_py( + "read_protect_efuse BLOCK_KEY4", + check_msg="A fatal error occurred: " + "BLOCK_KEY4 must be readable, stop this operation!", + ret_code=2, + ) + self.espefuse_py("read_protect_efuse BLOCK_KEY5") - @unittest.skipUnless(chip_target == "esp32", "system parameters efuse read-protection is supported only by esp32, other chips protect whole blocks") + @unittest.skipUnless( + chip_target == "esp32", + "system parameters efuse read-protection is supported only by esp32, " + "other chips protect whole blocks", + ) def test_burn_and_read_protect_efuse(self): - self.espefuse_py('burn_efuse FLASH_CRYPT_CONFIG 15 RD_DIS 8', - check_msg='Efuse FLASH_CRYPT_CONFIG is read-protected. Read back the burn value is not possible.') + self.espefuse_py( + "burn_efuse FLASH_CRYPT_CONFIG 15 RD_DIS 8", + check_msg="Efuse FLASH_CRYPT_CONFIG is read-protected. " + "Read back the burn value is not possible.", + ) class TestWriteProtectionCommands(EfuseTestCase): def test_write_protect_efuse(self): self.espefuse_py("write_protect_efuse -h") if chip_target == "esp32": - efuse_lists = '''WR_DIS RD_DIS CODING_SCHEME CHIP_VERSION CHIP_PACKAGE XPD_SDIO_FORCE - XPD_SDIO_REG XPD_SDIO_TIEH SPI_PAD_CONFIG_CLK FLASH_CRYPT_CNT UART_DOWNLOAD_DIS - FLASH_CRYPT_CONFIG ADC_VREF BLOCK1 BLOCK2 BLOCK3''' - efuse_lists2 = 'WR_DIS RD_DIS' + efuse_lists = """WR_DIS RD_DIS CODING_SCHEME CHIP_VERSION CHIP_PACKAGE + XPD_SDIO_FORCE XPD_SDIO_REG XPD_SDIO_TIEH SPI_PAD_CONFIG_CLK + FLASH_CRYPT_CNT UART_DOWNLOAD_DIS FLASH_CRYPT_CONFIG + ADC_VREF BLOCK1 BLOCK2 BLOCK3""" + efuse_lists2 = "WR_DIS RD_DIS" elif chip_target == "esp32c2": - efuse_lists = '''RD_DIS DIS_DOWNLOAD_ICACHE - XTS_KEY_LENGTH_256 UART_PRINT_CONTROL''' - efuse_lists2 = 'RD_DIS DIS_DOWNLOAD_ICACHE' + efuse_lists = """RD_DIS DIS_DOWNLOAD_ICACHE + XTS_KEY_LENGTH_256 UART_PRINT_CONTROL""" + efuse_lists2 = "RD_DIS DIS_DOWNLOAD_ICACHE" else: - efuse_lists = '''RD_DIS DIS_ICACHE DIS_DOWNLOAD_ICACHE DIS_FORCE_DOWNLOAD - DIS_CAN SOFT_DIS_JTAG DIS_DOWNLOAD_MANUAL_ENCRYPT USB_EXCHG_PINS - WDT_DELAY_SEL SPI_BOOT_CRYPT_CNT SECURE_BOOT_KEY_REVOKE0 - SECURE_BOOT_KEY_REVOKE1 SECURE_BOOT_KEY_REVOKE2 KEY_PURPOSE_0 KEY_PURPOSE_1 KEY_PURPOSE_2 KEY_PURPOSE_3 KEY_PURPOSE_4 KEY_PURPOSE_5 - SECURE_BOOT_EN SECURE_BOOT_AGGRESSIVE_REVOKE FLASH_TPUW DIS_DOWNLOAD_MODE DIS_LEGACY_SPI_BOOT UART_PRINT_CHANNEL - DIS_USB_DOWNLOAD_MODE ENABLE_SECURITY_DOWNLOAD UART_PRINT_CONTROL - MAC SPI_PAD_CONFIG_CLK SPI_PAD_CONFIG_Q SPI_PAD_CONFIG_D SPI_PAD_CONFIG_CS SPI_PAD_CONFIG_HD SPI_PAD_CONFIG_WP SPI_PAD_CONFIG_DQS - SPI_PAD_CONFIG_D4 SPI_PAD_CONFIG_D5 SPI_PAD_CONFIG_D6 SPI_PAD_CONFIG_D7 WAFER_VERSION PKG_VERSION BLOCK1_VERSION OPTIONAL_UNIQUE_ID - BLOCK2_VERSION BLOCK_USR_DATA BLOCK_KEY0 BLOCK_KEY1 BLOCK_KEY2 BLOCK_KEY3 BLOCK_KEY4 BLOCK_KEY5''' - efuse_lists2 = 'RD_DIS DIS_ICACHE' - self.espefuse_py('write_protect_efuse {}'.format(efuse_lists)) - output = self.espefuse_py('write_protect_efuse {}'.format(efuse_lists2)) + efuse_lists = """RD_DIS DIS_ICACHE DIS_DOWNLOAD_ICACHE DIS_FORCE_DOWNLOAD + DIS_CAN SOFT_DIS_JTAG DIS_DOWNLOAD_MANUAL_ENCRYPT + USB_EXCHG_PINS WDT_DELAY_SEL SPI_BOOT_CRYPT_CNT + SECURE_BOOT_KEY_REVOKE0 SECURE_BOOT_KEY_REVOKE1 + SECURE_BOOT_KEY_REVOKE2 KEY_PURPOSE_0 KEY_PURPOSE_1 + KEY_PURPOSE_2 KEY_PURPOSE_3 KEY_PURPOSE_4 KEY_PURPOSE_5 + SECURE_BOOT_EN SECURE_BOOT_AGGRESSIVE_REVOKE FLASH_TPUW + DIS_DOWNLOAD_MODE DIS_LEGACY_SPI_BOOT UART_PRINT_CHANNEL + DIS_USB_DOWNLOAD_MODE ENABLE_SECURITY_DOWNLOAD + UART_PRINT_CONTROL MAC SPI_PAD_CONFIG_CLK SPI_PAD_CONFIG_Q + SPI_PAD_CONFIG_D SPI_PAD_CONFIG_CS SPI_PAD_CONFIG_HD + SPI_PAD_CONFIG_WP SPI_PAD_CONFIG_DQS SPI_PAD_CONFIG_D4 + SPI_PAD_CONFIG_D5 SPI_PAD_CONFIG_D6 SPI_PAD_CONFIG_D7 + WAFER_VERSION PKG_VERSION BLOCK1_VERSION OPTIONAL_UNIQUE_ID + BLOCK2_VERSION BLOCK_USR_DATA BLOCK_KEY0 BLOCK_KEY1 + BLOCK_KEY2 BLOCK_KEY3 BLOCK_KEY4 BLOCK_KEY5""" + efuse_lists2 = "RD_DIS DIS_ICACHE" + self.espefuse_py("write_protect_efuse {}".format(efuse_lists)) + output = self.espefuse_py("write_protect_efuse {}".format(efuse_lists2)) self.assertEqual(2, output.count("is already write protected")) def test_write_protect_efuse2(self): if chip_target == "esp32": - self.espefuse_py('write_protect_efuse WR_DIS') - self.espefuse_py('write_protect_efuse CODING_SCHEME', - check_msg='A fatal error occurred: This efuse cannot be write-disabled due to the WR_DIS field is already write-disabled', - ret_code=2) + self.espefuse_py("write_protect_efuse WR_DIS") + self.espefuse_py( + "write_protect_efuse CODING_SCHEME", + check_msg="A fatal error occurred: This efuse cannot be write-disabled " + "due to the WR_DIS field is already write-disabled", + ret_code=2, + ) class TestBurnCustomMacCommands(EfuseTestCase): def test_burn_custom_mac(self): self.espefuse_py("burn_custom_mac -h") - cmd = 'burn_custom_mac AA:CD:EF:11:22:33' + cmd = "burn_custom_mac AA:CD:EF:11:22:33" if chip_target == "esp32": - self.espefuse_py(cmd, check_msg='Custom MAC Address version 1: aa:cd:ef:11:22:33 (CRC 0x63 OK)') + self.espefuse_py( + cmd, + check_msg="Custom MAC Address version 1: " + "aa:cd:ef:11:22:33 (CRC 0x63 OK)", + ) else: - mac_custom = 'aa:cd:ef:11:22:33:00:00' if chip_target == "esp32h2beta1" else 'aa:cd:ef:11:22:33' - self.espefuse_py(cmd, check_msg='Custom MAC Address: %s (OK)' % mac_custom) + mac_custom = ( + "aa:cd:ef:11:22:33:00:00" + if chip_target == "esp32h2beta1" + else "aa:cd:ef:11:22:33" + ) + self.espefuse_py(cmd, check_msg="Custom MAC Address: %s (OK)" % mac_custom) def test_burn_custom_mac2(self): - self.espefuse_py('burn_custom_mac AA:CD:EF:11:22:33:44', - check_msg='A fatal error occurred: MAC Address needs to be a 6-byte hexadecimal format separated by colons (:)!', - ret_code=2) + self.espefuse_py( + "burn_custom_mac AA:CD:EF:11:22:33:44", + check_msg="A fatal error occurred: MAC Address needs to be a 6-byte " + "hexadecimal format separated by colons (:)!", + ret_code=2, + ) def test_burn_custom_mac3(self): - self.espefuse_py('burn_custom_mac AB:CD:EF:11:22:33', - check_msg='A fatal error occurred: Custom MAC must be a unicast MAC!', - ret_code=2) + self.espefuse_py( + "burn_custom_mac AB:CD:EF:11:22:33", + check_msg="A fatal error occurred: Custom MAC must be a unicast MAC!", + ret_code=2, + ) @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_custom_mac_with_34_coding_scheme(self): self._set_34_coding_scheme() self.espefuse_py("burn_custom_mac -h") - self.espefuse_py('burn_custom_mac AA:CD:EF:01:02:03', check_msg='Custom MAC Address version 1: aa:cd:ef:01:02:03 (CRC 0x56 OK)') - self.espefuse_py('get_custom_mac', check_msg='Custom MAC Address version 1: aa:cd:ef:01:02:03 (CRC 0x56 OK)') + self.espefuse_py( + "burn_custom_mac AA:CD:EF:01:02:03", + check_msg="Custom MAC Address version 1: aa:cd:ef:01:02:03 (CRC 0x56 OK)", + ) + self.espefuse_py( + "get_custom_mac", + check_msg="Custom MAC Address version 1: aa:cd:ef:01:02:03 (CRC 0x56 OK)", + ) - self.espefuse_py('burn_custom_mac FE:22:33:44:55:66', - check_msg='New value contains some bits that cannot be cleared (value will be 0x675745ffeffe)', - ret_code=2) + self.espefuse_py( + "burn_custom_mac FE:22:33:44:55:66", + check_msg="New value contains some bits that cannot be cleared " + "(value will be 0x675745ffeffe)", + ret_code=2, + ) -@unittest.skipIf(chip_target == "esp32c2", "TODO: add support set_flash_voltage for ESP32-C2") -@unittest.skipIf(chip_target == "esp32h2beta1", "TODO: add support set_flash_voltage for ESP32-H2") -@unittest.skipIf(chip_target == "esp32c3", "TODO: add support set_flash_voltage for ESP32-C3") +@unittest.skipIf( + chip_target == "esp32c2", "TODO: add support set_flash_voltage for ESP32-C2" +) +@unittest.skipIf( + chip_target == "esp32h2beta1", "TODO: add support set_flash_voltage for ESP32-H2" +) +@unittest.skipIf( + chip_target == "esp32c3", "TODO: add support set_flash_voltage for ESP32-C3" +) class TestSetFlashVoltageCommands(EfuseTestCase): def test_set_flash_voltage_1_8v(self): self.espefuse_py("set_flash_voltage -h") vdd = "VDD_SDIO" if chip_target == "esp32" else "VDD_SPI" - self.espefuse_py('set_flash_voltage 1.8V', check_msg='Set internal flash voltage regulator (%s) to 1.8V.' % vdd) + self.espefuse_py( + "set_flash_voltage 1.8V", + check_msg="Set internal flash voltage regulator (%s) to 1.8V." % vdd, + ) if chip_target == "esp32": - error_msg = "A fatal error occurred: Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned" + error_msg = "A fatal error occurred: " + "Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned" else: - error_msg = "A fatal error occurred: Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned" - self.espefuse_py('set_flash_voltage 3.3V', check_msg='Enable internal flash voltage regulator (%s) to 3.3V.' % vdd) - self.espefuse_py('set_flash_voltage OFF', check_msg=error_msg, ret_code=2) + error_msg = "A fatal error occurred: " + "Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned" + self.espefuse_py( + "set_flash_voltage 3.3V", + check_msg="Enable internal flash voltage regulator (%s) to 3.3V." % vdd, + ) + self.espefuse_py("set_flash_voltage OFF", check_msg=error_msg, ret_code=2) def test_set_flash_voltage_3_3v(self): vdd = "VDD_SDIO" if chip_target == "esp32" else "VDD_SPI" - self.espefuse_py('set_flash_voltage 3.3V', check_msg='Enable internal flash voltage regulator (%s) to 3.3V.' % vdd) + self.espefuse_py( + "set_flash_voltage 3.3V", + check_msg="Enable internal flash voltage regulator (%s) to 3.3V." % vdd, + ) if chip_target == "esp32": - error_msg = "A fatal error occurred: Can't set regulator to 1.8V is XPD_SDIO_TIEH efuse is already burned" + error_msg = "A fatal error occurred: " + "Can't set regulator to 1.8V is XPD_SDIO_TIEH efuse is already burned" else: - error_msg = "A fatal error occurred: Can't set regulator to 1.8V is VDD_SPI_TIEH efuse is already burned" - self.espefuse_py('set_flash_voltage 1.8V', check_msg=error_msg, ret_code=2) + error_msg = "A fatal error occurred: " + "Can't set regulator to 1.8V is VDD_SPI_TIEH efuse is already burned" + self.espefuse_py("set_flash_voltage 1.8V", check_msg=error_msg, ret_code=2) if chip_target == "esp32": - error_msg = "A fatal error occurred: Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned" + error_msg = "A fatal error occurred: " + "Can't set flash regulator to OFF as XPD_SDIO_REG efuse is already burned" else: - error_msg = "A fatal error occurred: Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned" - self.espefuse_py('set_flash_voltage OFF', check_msg=error_msg, ret_code=2) + error_msg = "A fatal error occurred: " + "Can't set flash regulator to OFF as VDD_SPI_XPD efuse is already burned" + self.espefuse_py("set_flash_voltage OFF", check_msg=error_msg, ret_code=2) def test_set_flash_voltage_off(self): vdd = "VDD_SDIO" if chip_target == "esp32" else "VDD_SPI" - self.espefuse_py('set_flash_voltage OFF', check_msg='Disable internal flash voltage regulator (%s)' % vdd) - self.espefuse_py('set_flash_voltage 3.3V', check_msg='Enable internal flash voltage regulator (%s) to 3.3V.' % vdd) + self.espefuse_py( + "set_flash_voltage OFF", + check_msg="Disable internal flash voltage regulator (%s)" % vdd, + ) + self.espefuse_py( + "set_flash_voltage 3.3V", + check_msg="Enable internal flash voltage regulator (%s) to 3.3V." % vdd, + ) def test_set_flash_voltage_off2(self): vdd = "VDD_SDIO" if chip_target == "esp32" else "VDD_SPI" - self.espefuse_py('set_flash_voltage OFF', check_msg='Disable internal flash voltage regulator (%s)' % vdd) - self.espefuse_py('set_flash_voltage 1.8V', check_msg='Set internal flash voltage regulator (%s) to 1.8V.' % vdd) + self.espefuse_py( + "set_flash_voltage OFF", + check_msg="Disable internal flash voltage regulator (%s)" % vdd, + ) + self.espefuse_py( + "set_flash_voltage 1.8V", + check_msg="Set internal flash voltage regulator (%s) to 1.8V." % vdd, + ) class TestBurnEfuseCommands(EfuseTestCase): - @unittest.skipUnless(chip_target == "esp32", "IO pins 30 & 31 cannot be set for SPI flash only on esp32") + @unittest.skipUnless( + chip_target == "esp32", + "IO pins 30 & 31 cannot be set for SPI flash only on esp32", + ) def test_set_spi_flash_pin_efuses(self): - self.espefuse_py('burn_efuse SPI_PAD_CONFIG_HD 30', - check_msg='A fatal error occurred: IO pins 30 & 31 cannot be set for SPI flash. 0-29, 32 & 33 only.', ret_code=2) - self.espefuse_py('burn_efuse SPI_PAD_CONFIG_Q 0x23', - check_msg='A fatal error occurred: IO pin 35 cannot be set for SPI flash. 0-29, 32 & 33 only.', ret_code=2) - output = self.espefuse_py('burn_efuse SPI_PAD_CONFIG_CS0 33') - self.assertIn("(Override SD_CMD pad (GPIO11/SPICS0)) 0b00000 -> 0b11111", output) + self.espefuse_py( + "burn_efuse SPI_PAD_CONFIG_HD 30", + check_msg="A fatal error occurred: " + "IO pins 30 & 31 cannot be set for SPI flash. 0-29, 32 & 33 only.", + ret_code=2, + ) + self.espefuse_py( + "burn_efuse SPI_PAD_CONFIG_Q 0x23", + check_msg="A fatal error occurred: " + "IO pin 35 cannot be set for SPI flash. 0-29, 32 & 33 only.", + ret_code=2, + ) + output = self.espefuse_py("burn_efuse SPI_PAD_CONFIG_CS0 33") + self.assertIn( + "(Override SD_CMD pad (GPIO11/SPICS0)) 0b00000 -> 0b11111", output + ) self.assertIn("BURN BLOCK0 - OK (write block == read block)", output) def test_burn_mac_custom_efuse(self): - crc_msg = '(OK)' - self.espefuse_py('burn_efuse -h') - if chip_target == 'esp32': - self.espefuse_py('burn_efuse MAC AA:CD:EF:01:02:03', check_msg='Writing Factory MAC address is not supported', ret_code=2) - self.espefuse_py('burn_efuse MAC_VERSION 1') - crc_msg = '(CRC 0x56 OK)' - if chip_target == 'esp32c2': - self.espefuse_py('burn_efuse CUSTOM_MAC_USED 1') - self.espefuse_py('burn_efuse -h') - self.espefuse_py('burn_efuse CUSTOM_MAC AB:CD:EF:01:02:03', check_msg='A fatal error occurred: Custom MAC must be a unicast MAC!', ret_code=2) - self.espefuse_py('burn_efuse CUSTOM_MAC AA:CD:EF:01:02:03') - if chip_target in ['esp32h2', 'esp32h2beta1']: - self.espefuse_py('get_custom_mac', check_msg='aa:cd:ef:01:02:03:00:00 {}'.format(crc_msg)) + crc_msg = "(OK)" + self.espefuse_py("burn_efuse -h") + if chip_target == "esp32": + self.espefuse_py( + "burn_efuse MAC AA:CD:EF:01:02:03", + check_msg="Writing Factory MAC address is not supported", + ret_code=2, + ) + self.espefuse_py("burn_efuse MAC_VERSION 1") + crc_msg = "(CRC 0x56 OK)" + if chip_target == "esp32c2": + self.espefuse_py("burn_efuse CUSTOM_MAC_USED 1") + self.espefuse_py("burn_efuse -h") + self.espefuse_py( + "burn_efuse CUSTOM_MAC AB:CD:EF:01:02:03", + check_msg="A fatal error occurred: Custom MAC must be a unicast MAC!", + ret_code=2, + ) + self.espefuse_py("burn_efuse CUSTOM_MAC AA:CD:EF:01:02:03") + if chip_target in ["esp32h2", "esp32h2beta1"]: + self.espefuse_py( + "get_custom_mac", check_msg="aa:cd:ef:01:02:03:00:00 {}".format(crc_msg) + ) else: - self.espefuse_py('get_custom_mac', check_msg='aa:cd:ef:01:02:03 {}'.format(crc_msg)) + self.espefuse_py( + "get_custom_mac", check_msg="aa:cd:ef:01:02:03 {}".format(crc_msg) + ) def test_burn_efuse(self): self.espefuse_py("burn_efuse -h") if chip_target == "esp32": - self.espefuse_py('burn_efuse \ - CHIP_VER_REV2 1 \ - DISABLE_DL_ENCRYPT 1 \ - CONSOLE_DEBUG_DISABLE 1') + self.espefuse_py( + "burn_efuse \ + CHIP_VER_REV2 1 \ + DISABLE_DL_ENCRYPT 1 \ + CONSOLE_DEBUG_DISABLE 1" + ) blk1 = "BLOCK1" blk2 = "BLOCK2" elif chip_target == "esp32c2": - self.espefuse_py('burn_efuse \ - XTS_KEY_LENGTH_256 1 \ - UART_PRINT_CONTROL 1 \ - FORCE_SEND_RESUME 1') + self.espefuse_py( + "burn_efuse \ + XTS_KEY_LENGTH_256 1 \ + UART_PRINT_CONTROL 1 \ + FORCE_SEND_RESUME 1" + ) blk1 = "BLOCK_KEY0" blk2 = None else: - self.espefuse_py('burn_efuse \ - SECURE_BOOT_EN 1 \ - UART_PRINT_CONTROL 1') - self.espefuse_py('burn_efuse \ - OPTIONAL_UNIQUE_ID 0x2328ad5ac9145f698f843a26d6eae168', - check_msg="-> 0x2328ad5ac9145f698f843a26d6eae168") - output = self.espefuse_py('summary -d') - self.assertIn('read_regs: d6eae168 8f843a26 c9145f69 2328ad5a 00000000 00000000 00000000 00000000', output) - self.assertIn('= 68 e1 ea d6 26 3a 84 8f 69 5f 14 c9 5a ad 28 23 R/W', output) - self.espefuse_py('burn_efuse \ - BLOCK2_VERSION 1', - check_msg="Burn into BLOCK_SYS_DATA is forbidden (RS coding scheme does not allow this).", - ret_code=2) + self.espefuse_py( + "burn_efuse \ + SECURE_BOOT_EN 1 \ + UART_PRINT_CONTROL 1" + ) + self.espefuse_py( + "burn_efuse \ + OPTIONAL_UNIQUE_ID 0x2328ad5ac9145f698f843a26d6eae168", + check_msg="-> 0x2328ad5ac9145f698f843a26d6eae168", + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "read_regs: d6eae168 8f843a26 c9145f69 2328ad5a " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "= 68 e1 ea d6 26 3a 84 8f 69 5f 14 c9 5a ad 28 23 R/W", output + ) + self.espefuse_py( + "burn_efuse \ + BLOCK2_VERSION 1", + check_msg="Burn into BLOCK_SYS_DATA is forbidden " + "(RS coding scheme does not allow this).", + ret_code=2, + ) blk1 = "BLOCK_KEY1" blk2 = "BLOCK_KEY2" - output = self.espefuse_py('burn_efuse {} 0x00010203040506070809111111111111111111111111111111110000112233FF'.format(blk1)) - self.assertIn('-> 0x00010203040506070809111111111111111111111111111111110000112233ff', output) - output = self.espefuse_py('summary -d') - self.assertIn('read_regs: 112233ff 11110000 11111111 11111111 11111111 08091111 04050607 00010203', output) - self.assertIn('= ff 33 22 11 00 00 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 09 08 07 06 05 04 03 02 01 00 R/W', output) + output = self.espefuse_py( + "burn_efuse {}".format(blk1) + + " 0x00010203040506070809111111111111111111111111111111110000112233FF" + ) + self.assertIn( + "-> 0x00010203040506070809111111111111111111111111111111110000112233ff", + output, + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "read_regs: " + "112233ff 11110000 11111111 11111111 11111111 08091111 04050607 00010203", + output, + ) + self.assertIn( + "= ff 33 22 11 00 00 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 " + "09 08 07 06 05 04 03 02 01 00 R/W", + output, + ) if blk2 is not None: - output = self.espefuse_py('burn_efuse {} 00010203040506070809111111111111111111111111111111110000112233FF'.format(blk2)) - self.assertIn('-> 0xff33221100001111111111111111111111111111111109080706050403020100', output) - output = self.espefuse_py('summary -d') - self.assertIn('read_regs: 03020100 07060504 11110908 11111111 11111111 11111111 00001111 ff332211', output) - self.assertIn('= 00 01 02 03 04 05 06 07 08 09 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 00 00 11 22 33 ff R/W', output) + output = self.espefuse_py( + "burn_efuse {}".format(blk2) + + " 00010203040506070809111111111111111111111111111111110000112233FF" + ) + self.assertIn( + "-> 0xff33221100001111111111111111111111111111111109080706050403020100", + output, + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "read_regs: 03020100 07060504 11110908 " + "11111111 11111111 11111111 00001111 ff332211", + output, + ) + self.assertIn( + "= 00 01 02 03 04 05 06 07 08 09 " + "11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 00 00 11 22 33 ff R/W", + output, + ) @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_efuse_with_34_coding_scheme(self): self._set_34_coding_scheme() self.espefuse_py("burn_efuse BLK3_PART_RESERVE 1") self.espefuse_py("burn_efuse ADC1_TP_LOW 50") - self.espefuse_py("burn_efuse ADC1_TP_HIGH 55", check_msg="Burn into BLOCK3 is forbidden (3/4 coding scheme does not allow this)", ret_code=2) + self.espefuse_py( + "burn_efuse ADC1_TP_HIGH 55", + check_msg="Burn into BLOCK3 is forbidden " + "(3/4 coding scheme does not allow this)", + ret_code=2, + ) @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_efuse_with_34_coding_scheme2(self): self._set_34_coding_scheme() self.espefuse_py("burn_efuse BLK3_PART_RESERVE 1") - self.espefuse_py("burn_efuse \ - ADC1_TP_LOW 50 \ - ADC1_TP_HIGH 55 \ - ADC2_TP_LOW 40 \ - ADC2_TP_HIGH 45") + self.espefuse_py( + "burn_efuse \ + ADC1_TP_LOW 50 \ + ADC1_TP_HIGH 55 \ + ADC2_TP_LOW 40 \ + ADC2_TP_HIGH 45" + ) class TestBurnKeyCommands(EfuseTestCase): @unittest.skipUnless(chip_target == "esp32", "The test only for esp32") def test_burn_key_3_key_blocks(self): self.espefuse_py("burn_key -h") - self.espefuse_py('burn_key BLOCK1 images/efuse/192bit', - check_msg="A fatal error occurred: Incorrect key file size 24. Key file must be 32 bytes (256 bits) of raw binary key data.", - ret_code=2) - self.espefuse_py('burn_key \ - BLOCK1 images/efuse/256bit \ - BLOCK2 images/efuse/256bit_1 \ - BLOCK3 images/efuse/256bit_2 --no-protect-key') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key BLOCK1 images/efuse/192bit", + check_msg="A fatal error occurred: Incorrect key file size 24. " + "Key file must be 32 bytes (256 bits) of raw binary key data.", + ret_code=2, + ) + self.espefuse_py( + "burn_key \ + BLOCK1 images/efuse/256bit \ + BLOCK2 images/efuse/256bit_1 \ + BLOCK3 images/efuse/256bit_2 --no-protect-key" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/256bit") self.check_data_block_in_log(output, "images/efuse/256bit_1") self.check_data_block_in_log(output, "images/efuse/256bit_2") - self.espefuse_py('burn_key \ - BLOCK1 images/efuse/256bit \ - BLOCK2 images/efuse/256bit_1 \ - BLOCK3 images/efuse/256bit_2') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key \ + BLOCK1 images/efuse/256bit \ + BLOCK2 images/efuse/256bit_1 \ + BLOCK3 images/efuse/256bit_2" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/256bit") self.check_data_block_in_log(output, "images/efuse/256bit_1") self.check_data_block_in_log(output, "images/efuse/256bit_2") @@ -483,59 +703,112 @@ class TestBurnKeyCommands(EfuseTestCase): @unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2") def test_burn_key_1_key_block(self): self.espefuse_py("burn_key -h") - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/128bit XTS_AES_128_KEY', - check_msg="A fatal error occurred: Incorrect key file size 16. Key file must be 32 bytes (256 bits) of raw binary key data.", - ret_code=2) - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY --no-read-protect') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/128bit XTS_AES_128_KEY", + check_msg="A fatal error occurred: Incorrect key file size 16. " + "Key file must be 32 bytes (256 bits) of raw binary key data.", + ret_code=2, + ) + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY --no-read-protect" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/256bit", reverse_order=True) - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + self.espefuse_py("burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY") + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) @unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2") def test_burn_key_one_key_block_with_fe_and_sb_keys(self): self.espefuse_py("burn_key -h") - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY \ - BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST', - check_msg="A fatal error occurred: These keypurposes are incompatible ['XTS_AES_128_KEY', 'SECURE_BOOT_DIGEST']", - ret_code=2) - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/128bit_key XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS \ - BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST --no-read-protect') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: 0c0d0e0f 08090a0b 04050607 00010203 03020100 07060504 0b0a0908 0f0e0d0c', output) + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/256bit XTS_AES_128_KEY \ + BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST", + check_msg="A fatal error occurred: These keypurposes are incompatible " + "['XTS_AES_128_KEY', 'SECURE_BOOT_DIGEST']", + ret_code=2, + ) + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/128bit_key " + "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS " + "BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST --no-read-protect" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: 0c0d0e0f 08090a0b 04050607 00010203 " + "03020100 07060504 0b0a0908 0f0e0d0c", + output, + ) - self.espefuse_py('burn_key BLOCK_KEY0 images/efuse/128bit_key XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS \ - BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: 00000000 00000000 00000000 00000000 03020100 07060504 0b0a0908 0f0e0d0c', output) + self.espefuse_py( + "burn_key BLOCK_KEY0 images/efuse/128bit_key " + "XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS " + "BLOCK_KEY0 images/efuse/128bit_key SECURE_BOOT_DIGEST" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: 00000000 00000000 00000000 00000000 " + "03020100 07060504 0b0a0908 0f0e0d0c", + output, + ) - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], "Only chip with 6 keys") + @unittest.skipUnless( + chip_target + in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], + "Only chip with 6 keys", + ) def test_burn_key_with_6_keys(self): - self.espefuse_py('burn_key \ - BLOCK_KEY0 images/efuse/256bit XTS_AES_256_KEY_1 \ - BLOCK_KEY1 images/efuse/256bit_1 XTS_AES_256_KEY_2 \ - BLOCK_KEY2 images/efuse/256bit_2 XTS_AES_128_KEY --no-read-protect --no-write-protect') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key \ + BLOCK_KEY0 images/efuse/256bit XTS_AES_256_KEY_1 \ + BLOCK_KEY1 images/efuse/256bit_1 XTS_AES_256_KEY_2 \ + BLOCK_KEY2 images/efuse/256bit_2 XTS_AES_128_KEY \ + --no-read-protect --no-write-protect" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/256bit", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_2", reverse_order=True) + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_2", reverse_order=True + ) - self.espefuse_py('burn_key \ - BLOCK_KEY0 images/efuse/256bit XTS_AES_256_KEY_1 \ - BLOCK_KEY1 images/efuse/256bit_1 XTS_AES_256_KEY_2 \ - BLOCK_KEY2 images/efuse/256bit_2 XTS_AES_128_KEY') - output = self.espefuse_py('summary -d') - self.assertIn('[4 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[5 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[6 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + self.espefuse_py( + "burn_key \ + BLOCK_KEY0 images/efuse/256bit XTS_AES_256_KEY_1 \ + BLOCK_KEY1 images/efuse/256bit_1 XTS_AES_256_KEY_2 \ + BLOCK_KEY2 images/efuse/256bit_2 XTS_AES_128_KEY" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[4 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[5 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[6 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) - self.espefuse_py('burn_key \ - BLOCK_KEY3 images/efuse/256bit SECURE_BOOT_DIGEST0 \ - BLOCK_KEY4 images/efuse/256bit_1 SECURE_BOOT_DIGEST1 \ - BLOCK_KEY5 images/efuse/256bit_2 SECURE_BOOT_DIGEST2') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key \ + BLOCK_KEY3 images/efuse/256bit SECURE_BOOT_DIGEST0 \ + BLOCK_KEY4 images/efuse/256bit_1 SECURE_BOOT_DIGEST1 \ + BLOCK_KEY5 images/efuse/256bit_2 SECURE_BOOT_DIGEST2" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/256bit") self.check_data_block_in_log(output, "images/efuse/256bit_1") self.check_data_block_in_log(output, "images/efuse/256bit_2") @@ -543,73 +816,133 @@ class TestBurnKeyCommands(EfuseTestCase): @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_key_with_34_coding_scheme(self): self._set_34_coding_scheme() - self.espefuse_py('burn_key BLOCK1 images/efuse/256bit', - check_msg="A fatal error occurred: Incorrect key file size 32. Key file must be 24 bytes (192 bits) of raw binary key data.", - ret_code=2) - self.espefuse_py('burn_key \ - BLOCK1 images/efuse/192bit \ - BLOCK2 images/efuse/192bit_1 \ - BLOCK3 images/efuse/192bit_2 --no-protect-key') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key BLOCK1 images/efuse/256bit", + check_msg="A fatal error occurred: Incorrect key file size 32. " + "Key file must be 24 bytes (192 bits) of raw binary key data.", + ret_code=2, + ) + self.espefuse_py( + "burn_key \ + BLOCK1 images/efuse/192bit \ + BLOCK2 images/efuse/192bit_1 \ + BLOCK3 images/efuse/192bit_2 --no-protect-key" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/192bit") self.check_data_block_in_log(output, "images/efuse/192bit_1") self.check_data_block_in_log(output, "images/efuse/192bit_2") - self.espefuse_py('burn_key \ - BLOCK1 images/efuse/192bit \ - BLOCK2 images/efuse/192bit_1 \ - BLOCK3 images/efuse/192bit_2') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_key \ + BLOCK1 images/efuse/192bit \ + BLOCK2 images/efuse/192bit_1 \ + BLOCK3 images/efuse/192bit_2" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/192bit") self.check_data_block_in_log(output, "images/efuse/192bit_1") self.check_data_block_in_log(output, "images/efuse/192bit_2") - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3"], "512 bit keys are only supported on ESP32-S2 and S3") + @unittest.skipUnless( + chip_target in ["esp32s2", "esp32s3"], + "512 bit keys are only supported on ESP32-S2 and S3", + ) def test_burn_key_512bit(self): - self.espefuse_py('burn_key \ - BLOCK_KEY0 images/efuse/256bit_1_256bit_2_combined XTS_AES_256_KEY --no-read-protect --no-write-protect') - output = self.espefuse_py('summary -d') - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_2", reverse_order=True) + self.espefuse_py( + "burn_key \ + BLOCK_KEY0 images/efuse/256bit_1_256bit_2_combined \ + XTS_AES_256_KEY --no-read-protect --no-write-protect" + ) + output = self.espefuse_py("summary -d") + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_2", reverse_order=True + ) - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3"], "512 bit keys are only supported on ESP32-S2 and S3") + @unittest.skipUnless( + chip_target in ["esp32s2", "esp32s3"], + "512 bit keys are only supported on ESP32-S2 and S3", + ) def test_burn_key_512bit_non_consecutive_blocks(self): - # Burn efuses seperately to test different kinds of "key used" detection criteria - self.espefuse_py('burn_key \ - BLOCK_KEY2 images/efuse/256bit XTS_AES_128_KEY') - self.espefuse_py('burn_key \ - BLOCK_KEY3 images/efuse/256bit USER --no-read-protect --no-write-protect') - self.espefuse_py('burn_key \ - BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST0') + # Burn efuses seperately to test different kinds + # of "key used" detection criteria + self.espefuse_py( + "burn_key \ + BLOCK_KEY2 images/efuse/256bit XTS_AES_128_KEY" + ) + self.espefuse_py( + "burn_key \ + BLOCK_KEY3 images/efuse/256bit USER --no-read-protect --no-write-protect" + ) + self.espefuse_py( + "burn_key \ + BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST0" + ) - self.espefuse_py('burn_key \ - BLOCK_KEY1 images/efuse/256bit_1_256bit_2_combined XTS_AES_256_KEY --no-read-protect --no-write-protect') + self.espefuse_py( + "burn_key \ + BLOCK_KEY1 images/efuse/256bit_1_256bit_2_combined \ + XTS_AES_256_KEY --no-read-protect --no-write-protect" + ) # Second half of key should burn to first available key block (BLOCK_KEY5) - output = self.espefuse_py('summary -d') - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_2", reverse_order=True) + output = self.espefuse_py("summary -d") + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_2", reverse_order=True + ) - self.assertIn('[5 ] read_regs: bcbd11bf b8b9babb b4b5b6b7 b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 11a1a2a3', output) - self.assertIn('[9 ] read_regs: bcbd22bf b8b9babb b4b5b6b7 b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 22a1a2a3', output) + self.assertIn( + "[5 ] read_regs: bcbd11bf b8b9babb b4b5b6b7 " + "b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 11a1a2a3", + output, + ) + self.assertIn( + "[9 ] read_regs: bcbd22bf b8b9babb b4b5b6b7 " + "b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 22a1a2a3", + output, + ) - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3"], "512 bit keys are only supported on ESP32-S2 and S3") + @unittest.skipUnless( + chip_target in ["esp32s2", "esp32s3"], + "512 bit keys are only supported on ESP32-S2 and S3", + ) def test_burn_key_512bit_non_consecutive_blocks_loop_around(self): - self.espefuse_py('burn_key \ - BLOCK_KEY2 images/efuse/256bit XTS_AES_128_KEY \ - BLOCK_KEY3 images/efuse/256bit USER \ - BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST0 \ - BLOCK_KEY5 images/efuse/256bit SECURE_BOOT_DIGEST1 \ - BLOCK_KEY1 images/efuse/256bit_1_256bit_2_combined XTS_AES_256_KEY --no-read-protect --no-write-protect') + self.espefuse_py( + "burn_key \ + BLOCK_KEY2 images/efuse/256bit XTS_AES_128_KEY \ + BLOCK_KEY3 images/efuse/256bit USER \ + BLOCK_KEY4 images/efuse/256bit SECURE_BOOT_DIGEST0 \ + BLOCK_KEY5 images/efuse/256bit SECURE_BOOT_DIGEST1 \ + BLOCK_KEY1 images/efuse/256bit_1_256bit_2_combined \ + XTS_AES_256_KEY --no-read-protect --no-write-protect" + ) # Second half of key should burn to first available key block (BLOCK_KEY0) - output = self.espefuse_py('summary -d') - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_2", reverse_order=True) + output = self.espefuse_py("summary -d") + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_2", reverse_order=True + ) - self.assertIn('[5 ] read_regs: bcbd11bf b8b9babb b4b5b6b7 b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 11a1a2a3', output) - self.assertIn('[4 ] read_regs: bcbd22bf b8b9babb b4b5b6b7 b0b1b2b3 acadaeaf a8a9aaab a4a5a6a7 22a1a2a3', output) + self.assertIn( + "[5 ] read_regs: bcbd11bf b8b9babb b4b5b6b7 b0b1b2b3 " + "acadaeaf a8a9aaab a4a5a6a7 11a1a2a3", + output, + ) + self.assertIn( + "[4 ] read_regs: bcbd22bf b8b9babb b4b5b6b7 b0b1b2b3 " + "acadaeaf a8a9aaab a4a5a6a7 22a1a2a3", + output, + ) class TestBurnBlockDataCommands(EfuseTestCase): @@ -617,144 +950,250 @@ class TestBurnBlockDataCommands(EfuseTestCase): self.espefuse_py("burn_block_data -h") blk0 = "BLOCK0" blk1 = "BLOCK1" - self.espefuse_py('burn_block_data \ - %s images/efuse/224bit \ - %s' % (blk0, blk1), - check_msg="A fatal error occurred: The number of block_name (2) and datafile (1) should be the same.", - ret_code=2) + self.espefuse_py( + "burn_block_data \ + %s images/efuse/224bit \ + %s" + % (blk0, blk1), + check_msg="A fatal error occurred: " + "The number of block_name (2) and datafile (1) should be the same.", + ret_code=2, + ) @unittest.skipUnless(chip_target == "esp32", "The test only for esp32") def test_burn_block_data_with_3_key_blocks(self): - self.espefuse_py('burn_block_data \ - BLOCK0 images/efuse/224bit \ - BLOCK3 images/efuse/256bit') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc', output) + self.espefuse_py( + "burn_block_data \ + BLOCK0 images/efuse/224bit \ + BLOCK3 images/efuse/256bit" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac " + "b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc", + output, + ) self.check_data_block_in_log(output, "images/efuse/256bit") - self.espefuse_py('burn_block_data \ - BLOCK2 images/efuse/256bit_1') - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/256bit_1") + self.espefuse_py( + "burn_block_data \ + BLOCK2 images/efuse/256bit_1" + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/256bit_1" + ) - self.espefuse_py('burn_block_data \ - BLOCK1 images/efuse/256bit_2') - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/256bit_2") + self.espefuse_py( + "burn_block_data \ + BLOCK1 images/efuse/256bit_2" + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/256bit_2" + ) @unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2") def test_burn_block_data_with_1_key_block(self): - self.espefuse_py('burn_block_data \ - BLOCK0 images/efuse/64bit \ - BLOCK1 images/efuse/96bit \ - BLOCK2 images/efuse/256bit \ - BLOCK3 images/efuse/256bit') - output = self.espefuse_py('summary -d') - self.assertIn('[0 ] read_regs: 00000001 0000000c', output) - self.assertIn('[1 ] read_regs: 03020100 07060504 000a0908', output) - self.assertIn('[2 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc', output) - self.assertIn('[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc', output) + self.espefuse_py( + "burn_block_data \ + BLOCK0 images/efuse/64bit \ + BLOCK1 images/efuse/96bit \ + BLOCK2 images/efuse/256bit \ + BLOCK3 images/efuse/256bit" + ) + output = self.espefuse_py("summary -d") + self.assertIn("[0 ] read_regs: 00000001 0000000c", output) + self.assertIn("[1 ] read_regs: 03020100 07060504 000a0908", output) + self.assertIn( + "[2 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac " + "b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc", + output, + ) + self.assertIn( + "[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac " + "b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc", + output, + ) - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], "Only chip with 6 keys") + @unittest.skipUnless( + chip_target + in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], + "Only chip with 6 keys", + ) def test_burn_block_data_with_6_keys(self): - self.espefuse_py('burn_block_data \ - BLOCK0 images/efuse/192bit \ - BLOCK3 images/efuse/256bit') - output = self.espefuse_py('summary -d') - self.assertIn('[0 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514', output) - self.assertIn('[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc', output) + self.espefuse_py( + "burn_block_data \ + BLOCK0 images/efuse/192bit \ + BLOCK3 images/efuse/256bit" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[0 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514", + output, + ) + self.assertIn( + "[3 ] read_regs: a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac " + "b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc", + output, + ) self.check_data_block_in_log(output, "images/efuse/256bit") - self.espefuse_py('burn_block_data \ - BLOCK10 images/efuse/256bit_1') - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/256bit_1") + self.espefuse_py( + "burn_block_data \ + BLOCK10 images/efuse/256bit_1" + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/256bit_1" + ) - self.espefuse_py('burn_block_data \ - BLOCK1 images/efuse/192bit \ - BLOCK5 images/efuse/256bit_1 \ - BLOCK6 images/efuse/256bit_2') - output = self.espefuse_py('summary -d') - self.assertIn('[1 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514', output) + self.espefuse_py( + "burn_block_data \ + BLOCK1 images/efuse/192bit \ + BLOCK5 images/efuse/256bit_1 \ + BLOCK6 images/efuse/256bit_2" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[1 ] read_regs: 00000000 07060500 00000908 00000000 13000000 00161514", + output, + ) self.check_data_block_in_log(output, "images/efuse/256bit") self.check_data_block_in_log(output, "images/efuse/256bit_1", 2) self.check_data_block_in_log(output, "images/efuse/256bit_2") def test_burn_block_data_check_errors(self): - self.espefuse_py('burn_block_data \ - BLOCK2 images/efuse/192bit \ - BLOCK2 images/efuse/192bit_1', - check_msg="A fatal error occurred: Found repeated", - ret_code=2) - self.espefuse_py('burn_block_data \ - BLOCK2 images/efuse/192bit \ - BLOCK3 images/efuse/192bit_1 \ - --offset 4', - check_msg="A fatal error occurred: The 'offset' option is not applicable when a few blocks are passed.", - ret_code=2) - self.espefuse_py('burn_block_data BLOCK0 images/efuse/192bit --offset 33', - check_msg="A fatal error occurred: Invalid offset: the block0 only holds", - ret_code=2) - self.espefuse_py('burn_block_data BLOCK0 images/efuse/256bit --offset 4', - check_msg="A fatal error occurred: Data does not fit:", - ret_code=2) + self.espefuse_py( + "burn_block_data \ + BLOCK2 images/efuse/192bit \ + BLOCK2 images/efuse/192bit_1", + check_msg="A fatal error occurred: Found repeated", + ret_code=2, + ) + self.espefuse_py( + "burn_block_data \ + BLOCK2 images/efuse/192bit \ + BLOCK3 images/efuse/192bit_1 \ + --offset 4", + check_msg="A fatal error occurred: " + "The 'offset' option is not applicable when a few blocks are passed.", + ret_code=2, + ) + self.espefuse_py( + "burn_block_data BLOCK0 images/efuse/192bit --offset 33", + check_msg="A fatal error occurred: Invalid offset: the block0 only holds", + ret_code=2, + ) + self.espefuse_py( + "burn_block_data BLOCK0 images/efuse/256bit --offset 4", + check_msg="A fatal error occurred: Data does not fit:", + ret_code=2, + ) @unittest.skipUnless(chip_target == "esp32", "The test only for esp32") def test_burn_block_data_with_offset_for_3_key_blocks(self): offset = 1 - self.espefuse_py('burn_block_data --offset %d BLOCK0 images/efuse/192bit' % offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK0 images/efuse/192bit" % offset + ) offset = 4 - self.espefuse_py('burn_block_data --offset %d BLOCK1 images/efuse/192bit_1' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_1", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK1 images/efuse/192bit_1" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_1", offset=offset + ) offset = 6 - self.espefuse_py('burn_block_data --offset %d BLOCK2 images/efuse/192bit_2' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_2", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK2 images/efuse/192bit_2" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_2", offset=offset + ) offset = 8 - self.espefuse_py('burn_block_data --offset %d BLOCK3 images/efuse/192bit_2' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_2", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK3 images/efuse/192bit_2" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_2", offset=offset + ) @unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2") def test_burn_block_data_with_offset_1_key_block(self): offset = 4 - self.espefuse_py('burn_block_data --offset %d BLOCK1 images/efuse/92bit' % (offset)) - output = self.espefuse_py('summary -d') - self.assertIn('[1 ] read_regs: 00000000 03020100 00060504', output) + self.espefuse_py( + "burn_block_data --offset %d BLOCK1 images/efuse/92bit" % (offset) + ) + output = self.espefuse_py("summary -d") + self.assertIn("[1 ] read_regs: 00000000 03020100 00060504", output) offset = 6 - self.espefuse_py('burn_block_data --offset %d BLOCK2 images/efuse/192bit_1' % (offset)) - output = self.espefuse_py('summary -d') - self.assertIn('[2 ] read_regs: 00000000 00110000 05000000 09080706 0d0c0b0a 11100f0e 15141312 00002116', output) + self.espefuse_py( + "burn_block_data --offset %d BLOCK2 images/efuse/192bit_1" % (offset) + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[2 ] read_regs: 00000000 00110000 05000000 09080706 " + "0d0c0b0a 11100f0e 15141312 00002116", + output, + ) offset = 8 - self.espefuse_py('burn_block_data --offset %d BLOCK3 images/efuse/192bit_2' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_2", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK3 images/efuse/192bit_2" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_2", offset=offset + ) - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], "Only chip with 6 keys") + @unittest.skipUnless( + chip_target + in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], + "Only chip with 6 keys", + ) def test_burn_block_data_with_offset_6_keys(self): offset = 4 - self.espefuse_py('burn_block_data --offset %s BLOCK_KEY0 images/efuse/192bit_1' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_1", offset=offset) + self.espefuse_py( + "burn_block_data --offset %s BLOCK_KEY0 images/efuse/192bit_1" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_1", offset=offset + ) offset = 6 - self.espefuse_py('burn_block_data --offset %s BLOCK_KEY1 images/efuse/192bit_2' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_2", offset=offset) + self.espefuse_py( + "burn_block_data --offset %s BLOCK_KEY1 images/efuse/192bit_2" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_2", offset=offset + ) offset = 8 - self.espefuse_py('burn_block_data --offset %s BLOCK_KEY2 images/efuse/192bit_2' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/192bit_2", offset=offset) + self.espefuse_py( + "burn_block_data --offset %s BLOCK_KEY2 images/efuse/192bit_2" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/192bit_2", offset=offset + ) @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_block_data_with_34_coding_scheme(self): self._set_34_coding_scheme() - self.espefuse_py('burn_block_data BLOCK1 images/efuse/256bit', - check_msg="A fatal error occurred: Data does not fit: the block1 size is 24 bytes, data file is 32 bytes, offset 0", - ret_code=2) + self.espefuse_py( + "burn_block_data BLOCK1 images/efuse/256bit", + check_msg="A fatal error occurred: Data does not fit: " + "the block1 size is 24 bytes, data file is 32 bytes, offset 0", + ret_code=2, + ) - self.espefuse_py('burn_block_data \ - BLOCK1 images/efuse/192bit \ - BLOCK2 images/efuse/192bit_1 \ - BLOCK3 images/efuse/192bit_2') - output = self.espefuse_py('summary -d') + self.espefuse_py( + "burn_block_data \ + BLOCK1 images/efuse/192bit \ + BLOCK2 images/efuse/192bit_1 \ + BLOCK3 images/efuse/192bit_2" + ) + output = self.espefuse_py("summary -d") self.check_data_block_in_log(output, "images/efuse/192bit") self.check_data_block_in_log(output, "images/efuse/192bit_1") self.check_data_block_in_log(output, "images/efuse/192bit_2") @@ -764,194 +1203,388 @@ class TestBurnBlockDataCommands(EfuseTestCase): self._set_34_coding_scheme() offset = 4 - self.espefuse_py('burn_block_data --offset %d BLOCK1 images/efuse/128bit' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/128bit", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK1 images/efuse/128bit" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/128bit", offset=offset + ) offset = 6 - self.espefuse_py('burn_block_data --offset %d BLOCK2 images/efuse/128bit' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/128bit", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK2 images/efuse/128bit" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/128bit", offset=offset + ) offset = 8 - self.espefuse_py('burn_block_data --offset %d BLOCK3 images/efuse/128bit' % (offset)) - self.check_data_block_in_log(self.espefuse_py('summary -d'), "images/efuse/128bit", offset=offset) + self.espefuse_py( + "burn_block_data --offset %d BLOCK3 images/efuse/128bit" % (offset) + ) + self.check_data_block_in_log( + self.espefuse_py("summary -d"), "images/efuse/128bit", offset=offset + ) -@unittest.skipUnless(chip_target == "esp32", "The test only for esp32, supports 2 key blocks") +@unittest.skipUnless( + chip_target == "esp32", "The test only for esp32, supports 2 key blocks" +) class TestBurnKeyDigestCommandsEsp32(EfuseTestCase): def test_burn_key_digest(self): self.espefuse_py("burn_key_digest -h") esp = self.get_esptool() if "revision 3" in esp.get_chip_description(): - self.espefuse_py('burn_key_digest secure_images/rsa_secure_boot_signing_key.pem') - output = self.espefuse_py('summary -d') - self.assertIn(' = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-', output) + self.espefuse_py( + "burn_key_digest secure_images/rsa_secure_boot_signing_key.pem" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + " = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 " + "22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-", + output, + ) else: - self.espefuse_py('burn_key_digest secure_images/rsa_secure_boot_signing_key.pem', - check_msg="Incorrect chip revision for Secure boot v2.", - ret_code=2) + self.espefuse_py( + "burn_key_digest secure_images/rsa_secure_boot_signing_key.pem", + check_msg="Incorrect chip revision for Secure boot v2.", + ret_code=2, + ) def test_burn_key_from_digest(self): - # python espsecure.py digest_rsa_public_key --keyfile test/secure_images/rsa_secure_boot_signing_key.pem \ - # -o secure_images/rsa_public_key_digest.bin - self.espefuse_py('burn_key \ - BLOCK2 secure_images/rsa_public_key_digest.bin --no-protect-key') - output = self.espefuse_py('summary -d') - self.assertEqual(1, output.count(" = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/W")) + # python espsecure.py digest_rsa_public_key + # --keyfile test/secure_images/rsa_secure_boot_signing_key.pem + # -o secure_images/rsa_public_key_digest.bin + self.espefuse_py( + "burn_key \ + BLOCK2 secure_images/rsa_public_key_digest.bin --no-protect-key" + ) + output = self.espefuse_py("summary -d") + self.assertEqual( + 1, + output.count( + " = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 " + "22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/W" + ), + ) def test_burn_key_digest_with_34_coding_scheme(self): self._set_34_coding_scheme() - self.espefuse_py('burn_key_digest secure_images/rsa_secure_boot_signing_key.pem', - check_msg="burn_key_digest only works with 'None' coding scheme", - ret_code=2) + self.espefuse_py( + "burn_key_digest secure_images/rsa_secure_boot_signing_key.pem", + check_msg="burn_key_digest only works with 'None' coding scheme", + ret_code=2, + ) -@unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2, supports one key block") +@unittest.skipUnless( + chip_target == "esp32c2", "The test only for esp32c2, supports one key block" +) class TestBurnKeyDigestCommandsEsp32C2(EfuseTestCase): def test_burn_key_digest1(self): - # python espsecure.py generate_signing_key --version 2 secure_images/ecdsa192_secure_boot_signing_key_v2.pem --scheme ecdsa192 + # python espsecure.py generate_signing_key --version 2 + # secure_images/ecdsa192_secure_boot_signing_key_v2.pem --scheme ecdsa192 self.espefuse_py("burn_key_digest -h") - self.espefuse_py('burn_key_digest secure_images/ecdsa192_secure_boot_signing_key_v2.pem') - output = self.espefuse_py('summary -d') - self.assertIn(' = 1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-', output) - self.assertIn(' = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-', output) + self.espefuse_py( + "burn_key_digest secure_images/ecdsa192_secure_boot_signing_key_v2.pem" + ) + output = self.espefuse_py("summary -d") + self.assertIn(" = 1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-", output) + self.assertIn( + " = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-", + output, + ) def test_burn_key_digest2(self): - # python espsecure.py generate_signing_key --version 2 secure_images/ecdsa256_secure_boot_signing_key_v2.pem --scheme ecdsa256 + # python espsecure.py generate_signing_key --version 2 + # secure_images/ecdsa256_secure_boot_signing_key_v2.pem --scheme ecdsa256 self.espefuse_py("burn_key_digest -h") - self.espefuse_py('burn_key_digest secure_images/ecdsa256_secure_boot_signing_key_v2.pem') - output = self.espefuse_py('summary -d') - self.assertIn(' = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-', output) - self.assertIn(' = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-', output) + self.espefuse_py( + "burn_key_digest secure_images/ecdsa256_secure_boot_signing_key_v2.pem" + ) + output = self.espefuse_py("summary -d") + self.assertIn(" = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-", output) + self.assertIn( + " = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-", + output, + ) def test_burn_key_from_digest1(self): - # python espsecure.py digest_sbv2_public_key --keyfile secure_images/ecdsa192_secure_boot_signing_key_v2.pem \ - # -o secure_images/ecdsa192_public_key_digest_v2.bin - self.espefuse_py('burn_key BLOCK_KEY0 secure_images/ecdsa192_public_key_digest_v2.bin SECURE_BOOT_DIGEST') - output = self.espefuse_py('summary -d') - self.assertIn(' = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-', output) + # python espsecure.py digest_sbv2_public_key --keyfile + # secure_images/ecdsa192_secure_boot_signing_key_v2.pem + # -o secure_images/ecdsa192_public_key_digest_v2.bin + self.espefuse_py( + "burn_key BLOCK_KEY0 " + "secure_images/ecdsa192_public_key_digest_v2.bin SECURE_BOOT_DIGEST" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + " = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "1e 3d 15 16 96 ca 7f 22 a6 e8 8b d5 27 a0 3b 3b R/-", + output, + ) def test_burn_key_from_digest2(self): - # python espsecure.py digest_sbv2_public_key --keyfile secure_images/ecdsa256_secure_boot_signing_key_v2.pem \ - # -o secure_images/ecdsa256_public_key_digest_v2.bin - self.espefuse_py('burn_key BLOCK_KEY0 secure_images/ecdsa256_public_key_digest_v2.bin SECURE_BOOT_DIGEST') - output = self.espefuse_py('summary -d') - self.assertIn(' = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-', output) + # python espsecure.py digest_sbv2_public_key --keyfile + # secure_images/ecdsa256_secure_boot_signing_key_v2.pem + # -o secure_images/ecdsa256_public_key_digest_v2.bin + self.espefuse_py( + "burn_key BLOCK_KEY0 " + "secure_images/ecdsa256_public_key_digest_v2.bin SECURE_BOOT_DIGEST" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + " = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-", + output, + ) -@unittest.skipUnless(chip_target in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], "Supports 6 key blocks") +@unittest.skipUnless( + chip_target + in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], + "Supports 6 key blocks", +) class TestBurnKeyDigestCommands(EfuseTestCase): def test_burn_key_digest(self): self.espefuse_py("burn_key_digest -h") - self.espefuse_py('burn_key_digest \ - BLOCK_KEY0 secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0 \ - BLOCK_KEY1 secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST1 \ - BLOCK_KEY2 ', - check_msg="A fatal error occurred: The number of blocks (3), datafile (2) and keypurpose (2) should be the same.", - ret_code=2) - self.espefuse_py('burn_key_digest \ - BLOCK_KEY0 secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0 \ - BLOCK_KEY1 secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST1 \ - BLOCK_KEY2 secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST2') - output = self.espefuse_py('summary -d') - self.assertEqual(1, output.count(" = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-")) - self.assertEqual(2, output.count(" = 90 1a 74 09 23 8d 52 d4 cb f9 6f 56 3f b3 f4 29 6d ab d6 6a 33 f5 3b 15 ee cd 8c b3 e7 ec 45 d3 R/-")) + self.espefuse_py( + "burn_key_digest \ + BLOCK_KEY0 \ + secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0 \ + BLOCK_KEY1 \ + secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST1 \ + BLOCK_KEY2 ", + check_msg="A fatal error occurred: The number of blocks (3), " + "datafile (2) and keypurpose (2) should be the same.", + ret_code=2, + ) + self.espefuse_py( + "burn_key_digest \ + BLOCK_KEY0 \ + secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0 \ + BLOCK_KEY1 \ + secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST1 \ + BLOCK_KEY2 \ + secure_images/rsa_secure_boot_signing_key2.pem SECURE_BOOT_DIGEST2" + ) + output = self.espefuse_py("summary -d") + self.assertEqual( + 1, + output.count( + " = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 " + "22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-" + ), + ) + self.assertEqual( + 2, + output.count( + " = 90 1a 74 09 23 8d 52 d4 cb f9 6f 56 3f b3 f4 29 " + "6d ab d6 6a 33 f5 3b 15 ee cd 8c b3 e7 ec 45 d3 R/-" + ), + ) def test_burn_key_from_digest(self): - # python espsecure.py digest_rsa_public_key --keyfile test/secure_images/rsa_secure_boot_signing_key.pem \ - # -o secure_images/rsa_public_key_digest.bin - self.espefuse_py('burn_key \ - BLOCK_KEY0 secure_images/rsa_public_key_digest.bin SECURE_BOOT_DIGEST0') - output = self.espefuse_py('summary -d') - self.assertEqual(1, output.count(" = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-")) + # python espsecure.py digest_rsa_public_key + # --keyfile test/secure_images/rsa_secure_boot_signing_key.pem + # -o secure_images/rsa_public_key_digest.bin + self.espefuse_py( + "burn_key \ + BLOCK_KEY0 secure_images/rsa_public_key_digest.bin SECURE_BOOT_DIGEST0" + ) + output = self.espefuse_py("summary -d") + self.assertEqual( + 1, + output.count( + " = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 " + "22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-" + ), + ) - self.espefuse_py('burn_key_digest \ - BLOCK_KEY1 secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST1') - output = self.espefuse_py('summary -d') - self.assertEqual(2, output.count(" = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-")) + self.espefuse_py( + "burn_key_digest \ + BLOCK_KEY1 \ + secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST1" + ) + output = self.espefuse_py("summary -d") + self.assertEqual( + 2, + output.count( + " = cb 27 91 a3 71 b0 c0 32 2b f7 37 04 78 ba 09 62 " + "22 4c ab 1c f2 28 78 79 e4 29 67 3e 7d a8 44 63 R/-" + ), + ) class TestBurnBitCommands(EfuseTestCase): @unittest.skipUnless(chip_target == "esp32", "The test only for esp32") def test_burn_bit_for_chips_with_3_key_blocks(self): self.espefuse_py("burn_bit -h") - self.espefuse_py('burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255') - self.espefuse_py('summary', check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80") + self.espefuse_py("burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255") + self.espefuse_py( + "summary", + check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 00 " + "00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80", + ) - self.espefuse_py('burn_bit BLOCK3 3 5 6 7 9 10 11 12 13 14 15 31 63 95 127 159 191 223 254') - self.espefuse_py('summary', check_msg="ff ff 01 80 01 00 00 80 01 00 00 80 01 00 00 80 01 00 00 80 01 00 00 80 01 00 00 80 01 00 00 c0") + self.espefuse_py( + "burn_bit BLOCK3 3 5 6 7 9 10 11 12 13 14 15 31 63 95 127 159 191 223 254" + ) + self.espefuse_py( + "summary", + check_msg="ff ff 01 80 01 00 00 80 01 00 00 80 01 " + "00 00 80 01 00 00 80 01 00 00 80 01 00 00 80 01 00 00 c0", + ) @unittest.skipUnless(chip_target == "esp32c2", "The test only for esp32c2") def test_burn_bit_for_chips_with_1_key_block(self): self.espefuse_py("burn_bit -h") - self.espefuse_py('burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255') - self.espefuse_py('summary', check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80") - self.espefuse_py('burn_bit BLOCK3 100', check_msg="Burn into BLOCK_KEY0 is forbidden (RS coding scheme does not allow this)", ret_code=2) + self.espefuse_py("burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255") + self.espefuse_py( + "summary", + check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 " + "00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80", + ) + self.espefuse_py( + "burn_bit BLOCK3 100", + check_msg="Burn into BLOCK_KEY0 is forbidden " + "(RS coding scheme does not allow this)", + ret_code=2, + ) - self.espefuse_py('burn_bit BLOCK0 0 1 2') - self.espefuse_py('summary', check_msg="[0 ] read_regs: 00000007 00000000") + self.espefuse_py("burn_bit BLOCK0 0 1 2") + self.espefuse_py("summary", check_msg="[0 ] read_regs: 00000007 00000000") - @unittest.skipUnless(chip_target in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], "Only chip with 6 keys") + @unittest.skipUnless( + chip_target + in ["esp32s2", "esp32s3", "esp32s3beta1", "esp32c3", "esp32h2", "esp32h2beta1"], + "Only chip with 6 keys", + ) def test_burn_bit_for_chips_with_6_key_blocks(self): self.espefuse_py("burn_bit -h") - self.espefuse_py('burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255') - self.espefuse_py('summary', check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80") - self.espefuse_py('burn_bit BLOCK3 100', check_msg="Burn into BLOCK_USR_DATA is forbidden (RS coding scheme does not allow this)", ret_code=2) + self.espefuse_py("burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 192 224 255") + self.espefuse_py( + "summary", + check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 " + "00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80", + ) + self.espefuse_py( + "burn_bit BLOCK3 100", + check_msg="Burn into BLOCK_USR_DATA is forbidden " + "(RS coding scheme does not allow this)", + ret_code=2, + ) - self.espefuse_py('burn_bit BLOCK0 13') - self.espefuse_py('summary', check_msg="[0 ] read_regs: 00002000 00000000 00000000 00000000 00000000 00000000") + self.espefuse_py("burn_bit BLOCK0 13") + self.espefuse_py( + "summary", + check_msg="[0 ] read_regs: 00002000 00000000 00000000 " + "00000000 00000000 00000000", + ) - self.espefuse_py('burn_bit BLOCK0 24') - self.espefuse_py('summary', check_msg="[0 ] read_regs: 01002000 00000000 00000000 00000000 00000000 00000000") + self.espefuse_py("burn_bit BLOCK0 24") + self.espefuse_py( + "summary", + check_msg="[0 ] read_regs: 01002000 00000000 00000000 " + "00000000 00000000 00000000", + ) @unittest.skipUnless(chip_target == "esp32", "3/4 coding scheme is only in esp32") def test_burn_bit_with_34_coding_scheme(self): self._set_34_coding_scheme() - self.espefuse_py('burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 191') - self.espefuse_py('summary', check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 80") - self.espefuse_py('burn_bit BLOCK3 17', check_msg="Burn into BLOCK3 is forbidden (3/4 coding scheme does not allow this).", ret_code=2) + self.espefuse_py("burn_bit BLOCK3 0 1 2 4 8 16 32 64 96 128 160 191") + self.espefuse_py( + "summary", + check_msg="17 01 01 00 01 00 00 00 01 00 00 00 01 00 " + "00 00 01 00 00 00 01 00 00 80", + ) + self.espefuse_py( + "burn_bit BLOCK3 17", + check_msg="Burn into BLOCK3 is forbidden " + "(3/4 coding scheme does not allow this).", + ret_code=2, + ) -@unittest.skipUnless(chip_target == "esp32", "Tests are only for esp32. (TODO: add for all chips)") +@unittest.skipUnless( + chip_target == "esp32", "Tests are only for esp32. (TODO: add for all chips)" +) class TestByteOrderBurnKeyCommand(EfuseTestCase): def test_1_secure_boot_v1(self): if chip_target == "esp32": - self.espefuse_py('burn_key \ - flash_encryption images/efuse/256bit \ - secure_boot_v1 images/efuse/256bit_1 --no-protect-key') - output = self.espefuse_py('summary -d') - self.check_data_block_in_log(output, "images/efuse/256bit", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=True) + self.espefuse_py( + "burn_key \ + flash_encryption images/efuse/256bit \ + secure_boot_v1 images/efuse/256bit_1 --no-protect-key" + ) + output = self.espefuse_py("summary -d") + self.check_data_block_in_log( + output, "images/efuse/256bit", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=True + ) - self.espefuse_py('burn_key \ - flash_encryption images/efuse/256bit \ - secure_boot_v1 images/efuse/256bit_1') - output = self.espefuse_py('summary -d') - self.assertIn('[1 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[2 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[3 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + self.espefuse_py( + "burn_key \ + flash_encryption images/efuse/256bit \ + secure_boot_v1 images/efuse/256bit_1" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[1 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[2 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[3 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) def test_2_secure_boot_v1(self): if chip_target == "esp32": - self.espefuse_py('burn_key \ - flash_encryption images/efuse/256bit \ - secure_boot_v2 images/efuse/256bit_1 --no-protect-key') - output = self.espefuse_py('summary -d') - self.check_data_block_in_log(output, "images/efuse/256bit", reverse_order=True) - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=False) + self.espefuse_py( + "burn_key \ + flash_encryption images/efuse/256bit \ + secure_boot_v2 images/efuse/256bit_1 --no-protect-key" + ) + output = self.espefuse_py("summary -d") + self.check_data_block_in_log( + output, "images/efuse/256bit", reverse_order=True + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=False + ) - self.espefuse_py('burn_key \ - flash_encryption images/efuse/256bit \ - secure_boot_v2 images/efuse/256bit_1') - output = self.espefuse_py('summary -d') - self.assertIn('[1 ] read_regs: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.check_data_block_in_log(output, "images/efuse/256bit_1", reverse_order=False) + self.espefuse_py( + "burn_key \ + flash_encryption images/efuse/256bit \ + secure_boot_v2 images/efuse/256bit_1" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[1 ] read_regs: 00000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.check_data_block_in_log( + output, "images/efuse/256bit_1", reverse_order=False + ) class TestExecuteScriptsCommands(EfuseTestCase): @unittest.skipIf(chip_target == "esp32c2", "TODO: Add tests for esp32c2") def test_execute_scripts_with_check_that_only_one_burn(self): self.espefuse_py("execute_scripts -h") - name = chip_target if chip_target in ['esp32', 'esp32c2'] else 'esp32xx' + name = chip_target if chip_target in ["esp32", "esp32c2"] else "esp32xx" os.chdir(os.path.join(TEST_DIR, "efuse_scripts", name)) self.espefuse_py("execute_scripts test_efuse_script2.py") os.chdir(TEST_DIR) @@ -959,108 +1592,160 @@ class TestExecuteScriptsCommands(EfuseTestCase): @unittest.skipIf(chip_target == "esp32c2", "TODO: Add tests for esp32c2") def test_execute_scripts_with_check(self): self.espefuse_py("execute_scripts -h") - name = chip_target if chip_target in ['esp32', 'esp32c2'] else 'esp32xx' + name = chip_target if chip_target in ["esp32", "esp32c2"] else "esp32xx" os.chdir(os.path.join(TEST_DIR, "efuse_scripts", name)) self.espefuse_py("execute_scripts test_efuse_script.py") os.chdir(TEST_DIR) def test_execute_scripts_with_index_and_config(self): - if chip_target in ['esp32', 'esp32c2']: - cmd = "execute_scripts efuse_scripts/efuse_burn1.py --index 10 --configfiles efuse_scripts/esp32/config1.json" + if chip_target in ["esp32", "esp32c2"]: + cmd = "execute_scripts efuse_scripts/efuse_burn1.py --index 10 \ + --configfiles efuse_scripts/esp32/config1.json" else: - cmd = "execute_scripts efuse_scripts/efuse_burn1.py --index 10 --configfiles efuse_scripts/esp32xx/config1.json" + cmd = "execute_scripts efuse_scripts/efuse_burn1.py --index 10 \ + --configfiles efuse_scripts/esp32xx/config1.json" self.espefuse_py(cmd) - output = self.espefuse_py('summary -d') - if chip_target in ['esp32', 'esp32c2']: - self.assertIn('[3 ] read_regs: e00007ff 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + output = self.espefuse_py("summary -d") + if chip_target in ["esp32", "esp32c2"]: + self.assertIn( + "[3 ] read_regs: e00007ff 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) else: - self.assertIn('[8 ] read_regs: e00007ff 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + self.assertIn( + "[8 ] read_regs: e00007ff 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) def test_execute_scripts_nesting(self): - if chip_target in ['esp32', 'esp32c2']: - cmd = "execute_scripts efuse_scripts/efuse_burn2.py --index 28 --configfiles efuse_scripts/esp32/config2.json" + if chip_target in ["esp32", "esp32c2"]: + cmd = "execute_scripts efuse_scripts/efuse_burn2.py --index 28 \ + --configfiles efuse_scripts/esp32/config2.json" else: - cmd = "execute_scripts efuse_scripts/efuse_burn2.py --index 28 --configfiles efuse_scripts/esp32xx/config2.json" + cmd = "execute_scripts efuse_scripts/efuse_burn2.py --index 28 \ + --configfiles efuse_scripts/esp32xx/config2.json" self.espefuse_py(cmd) - output = self.espefuse_py('summary -d') - if chip_target in ['esp32', 'esp32c2']: - self.assertIn('[2 ] read_regs: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[3 ] read_regs: ffffffff 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + output = self.espefuse_py("summary -d") + if chip_target in ["esp32", "esp32c2"]: + self.assertIn( + "[2 ] read_regs: 10000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[3 ] read_regs: ffffffff 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) else: - self.assertIn('[7 ] read_regs: 10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) - self.assertIn('[8 ] read_regs: ffffffff 00000000 00000000 00000000 00000000 00000000 00000000 00000000', output) + self.assertIn( + "[7 ] read_regs: 10000000 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) + self.assertIn( + "[8 ] read_regs: ffffffff 00000000 00000000 00000000 " + "00000000 00000000 00000000 00000000", + output, + ) class TestMultipleCommands(EfuseTestCase): def test_multiple_cmds_help(self): if chip_target == "esp32c2": - command1 = 'burn_key_digest secure_images/ecdsa256_secure_boot_signing_key_v2.pem' - command2 = 'burn_key BLOCK_KEY0 images/efuse/128bit_key XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS' + command1 = ( + "burn_key_digest secure_images/ecdsa256_secure_boot_signing_key_v2.pem" + ) + command2 = "burn_key BLOCK_KEY0 images/efuse/128bit_key \ + XTS_AES_128_KEY_DERIVED_FROM_128_EFUSE_BITS" elif chip_target == "esp32": - command1 = 'burn_key_digest secure_images/rsa_secure_boot_signing_key.pem' - command2 = 'burn_key flash_encryption images/efuse/256bit' + command1 = "burn_key_digest secure_images/rsa_secure_boot_signing_key.pem" + command2 = "burn_key flash_encryption images/efuse/256bit" else: - command1 = 'burn_key_digest BLOCK_KEY0 secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0' - command2 = 'burn_key BLOCK_KEY0 secure_images/rsa_public_key_digest.bin SECURE_BOOT_DIGEST0' + command1 = "burn_key_digest BLOCK_KEY0 \ + secure_images/rsa_secure_boot_signing_key.pem SECURE_BOOT_DIGEST0" + command2 = "burn_key BLOCK_KEY0 \ + secure_images/rsa_public_key_digest.bin SECURE_BOOT_DIGEST0" - self.espefuse_py(' -h \ - {cmd1} \ - {cmd2} \ - '.format(cmd1=command1, cmd2=command2), - check_msg='usage: __init__.py [-h]') + self.espefuse_py( + "-h {cmd1} {cmd2}".format(cmd1=command1, cmd2=command2), + check_msg="usage: __init__.py [-h]", + ) - self.espefuse_py(' \ - {cmd1} -h \ - {cmd2} \ - '.format(cmd1=command1, cmd2=command2), - check_msg='usage: __init__.py burn_key_digest [-h]') + self.espefuse_py( + "{cmd1} -h {cmd2}".format(cmd1=command1, cmd2=command2), + check_msg="usage: __init__.py burn_key_digest [-h]", + ) - self.espefuse_py(' \ - {cmd1} \ - {cmd2} -h \ - '.format(cmd1=command1, cmd2=command2), - check_msg='usage: __init__.py burn_key [-h]') + self.espefuse_py( + "{cmd1} {cmd2} -h".format(cmd1=command1, cmd2=command2), + check_msg="usage: __init__.py burn_key [-h]", + ) - @unittest.skipUnless(chip_target == "esp32c2", "For this chip, FE and SB keys go into one BLOCK") + @unittest.skipUnless( + chip_target == "esp32c2", "For this chip, FE and SB keys go into one BLOCK" + ) def test_1_esp32c2(self): - self.espefuse_py('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 --no-read-protect \ - summary') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: 0c0d0e0f 08090a0b 04050607 00010203 f66a0fbf 8b6dd38b a9dab353 040af633', output) - self.assertIn(' = 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00 R/-', output) - self.assertIn(' = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-', output) + self.espefuse_py( + "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 --no-read-protect \ + summary" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: 0c0d0e0f 08090a0b 04050607 00010203 " + "f66a0fbf 8b6dd38b a9dab353 040af633", + output, + ) + self.assertIn(" = 0f 0e 0d 0c 0b 0a 09 08 07 06 05 04 03 02 01 00 R/-", output) + self.assertIn(" = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-", output) - @unittest.skipUnless(chip_target == "esp32c2", "For this chip, FE and SB keys go into one BLOCK") + @unittest.skipUnless( + chip_target == "esp32c2", "For this chip, FE and SB keys go into one BLOCK" + ) def test_2_esp32c2(self): - self.espefuse_py('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 \ - summary') - output = self.espefuse_py('summary -d') - self.assertIn('[3 ] read_regs: 00000000 00000000 00000000 00000000 f66a0fbf 8b6dd38b a9dab353 040af633', output) - self.assertIn(' = ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? -/-', output) - self.assertIn(' = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-', output) + self.espefuse_py( + "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 \ + summary" + ) + output = self.espefuse_py("summary -d") + self.assertIn( + "[3 ] read_regs: 00000000 00000000 00000000 00000000 " + "f66a0fbf 8b6dd38b a9dab353 040af633", + output, + ) + self.assertIn(" = ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? -/-", output) + self.assertIn(" = bf 0f 6a f6 8b d3 6d 8b 53 b3 da a9 33 f6 0a 04 R/-", output) def test_burn_bit(self): if chip_target == "esp32": self._set_34_coding_scheme() - self.espefuse_py('burn_bit BLOCK2 0 1 2 3 \ - burn_bit BLOCK2 4 5 6 7 \ - burn_bit BLOCK2 8 9 10 11 \ - burn_bit BLOCK2 12 13 14 15 \ - summary') - output = self.espefuse_py('summary -d') - self.assertIn('[2 ] read_regs: 0000ffff 00000000', output) + self.espefuse_py( + "burn_bit BLOCK2 0 1 2 3 \ + burn_bit BLOCK2 4 5 6 7 \ + burn_bit BLOCK2 8 9 10 11 \ + burn_bit BLOCK2 12 13 14 15 \ + summary" + ) + output = self.espefuse_py("summary -d") + self.assertIn("[2 ] read_regs: 0000ffff 00000000", output) def test_not_burn_cmds(self): - self.espefuse_py('summary \ - dump \ - get_custom_mac \ - adc_info \ - check_error') + self.espefuse_py( + "summary \ + dump \ + get_custom_mac \ + adc_info \ + check_error" + ) -if __name__ == '__main__': +if __name__ == "__main__": if len(sys.argv) > 1: chip_target = sys.argv[1] if chip_target not in support_list_chips: diff --git a/test/test_espsecure.py b/test/test_espsecure.py index 61b96b5..1dfe560 100755 --- a/test/test_espsecure.py +++ b/test/test_espsecure.py @@ -30,19 +30,20 @@ from test_esptool import ESPSECURE_PY class EspSecureTestCase(unittest.TestCase): - def run_espsecure(self, args): - """ Run espsecure.py with the specified arguments + """ + Run espsecure.py with the specified arguments - Returns output as a string if there is any, raises an exception if espsecure.py fails + Returns output as a string if there is any, + raises an exception if espsecure.py fails """ cmd = [sys.executable, ESPSECURE_PY] + args.split(" ") print("Running %s..." % (" ".join(cmd))) try: - output = subprocess.check_output([str(s) for s in cmd], - cwd=TEST_DIR, - stderr=subprocess.STDOUT) + output = subprocess.check_output( + [str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT + ) print(output) return output.decode("utf-8") except subprocess.CalledProcessError as e: @@ -57,51 +58,49 @@ class EspSecureTestCase(unittest.TestCase): f.close() def _open(self, image_file): - f = open(os.path.join('secure_images', image_file), 'rb') + f = open(os.path.join("secure_images", image_file), "rb") self.cleanup_files.append(f) return f class ESP32SecureBootloaderTests(EspSecureTestCase): - def test_digest_bootloader(self): - DBArgs = namedtuple('digest_bootloader_args', [ - 'keyfile', - 'output', - 'iv', - 'image']) + DBArgs = namedtuple( + "digest_bootloader_args", ["keyfile", "output", "iv", "image"] + ) try: output_file = tempfile.NamedTemporaryFile(delete=False) output_file.close() - args = DBArgs(self._open('256bit_key.bin'), - output_file.name, - self._open('256bit_iv.bin'), - self._open('bootloader.bin')) + args = DBArgs( + self._open("256bit_key.bin"), + output_file.name, + self._open("256bit_iv.bin"), + self._open("bootloader.bin"), + ) espsecure.digest_secure_bootloader(args) - with open(output_file.name, 'rb') as of: - with self._open('bootloader_digested.bin') as ef: + with open(output_file.name, "rb") as of: + with self._open("bootloader_digested.bin") as ef: self.assertEqual(ef.read(), of.read()) finally: os.unlink(output_file.name) def test_digest_rsa_public_key(self): - DigestRSAArgs = namedtuple('digest_rsa_public_key_args', [ - 'keyfile', - 'output']) + DigestRSAArgs = namedtuple("digest_rsa_public_key_args", ["keyfile", "output"]) try: output_file = tempfile.NamedTemporaryFile(delete=False) output_file.close() - args = DigestRSAArgs(self._open('rsa_secure_boot_signing_key.pem'), - output_file.name) + args = DigestRSAArgs( + self._open("rsa_secure_boot_signing_key.pem"), output_file.name + ) espsecure.digest_rsa_public_key(args) - with open(output_file.name, 'rb') as of: - with self._open('rsa_public_key_digest.bin') as ef: + with open(output_file.name, "rb") as of: + with self._open("rsa_public_key_digest.bin") as ef: self.assertEqual(ef.read(), of.read()) finally: os.unlink(output_file.name) @@ -109,27 +108,18 @@ class ESP32SecureBootloaderTests(EspSecureTestCase): class SigningTests(EspSecureTestCase): - VerifyArgs = namedtuple('verify_signature_args', [ - 'version', - 'keyfile', - 'datafile']) + VerifyArgs = namedtuple("verify_signature_args", ["version", "keyfile", "datafile"]) - SignArgs = namedtuple('sign_data_args', [ - 'version', - 'keyfile', - 'output', - 'append_signatures', - 'datafile']) + SignArgs = namedtuple( + "sign_data_args", + ["version", "keyfile", "output", "append_signatures", "datafile"], + ) - ExtractKeyArgs = namedtuple('extract_public_key_args', [ - 'version', - 'keyfile', - 'public_keyfile']) + ExtractKeyArgs = namedtuple( + "extract_public_key_args", ["version", "keyfile", "public_keyfile"] + ) - GenerateKeyArgs = namedtuple('generate_key_args', [ - 'version', - 'scheme', - 'keyfile']) + GenerateKeyArgs = namedtuple("generate_key_args", ["version", "scheme", "keyfile"]) def _test_sign_v1_data(self, key_name): try: @@ -138,307 +128,442 @@ class SigningTests(EspSecureTestCase): # Note: signing bootloader is not actually needed # for ESP32, it's just a handy file to sign - args = self.SignArgs('1', [self._open(key_name)], - output_file.name, None, - self._open('bootloader.bin')) + args = self.SignArgs( + "1", + [self._open(key_name)], + output_file.name, + None, + self._open("bootloader.bin"), + ) espsecure.sign_data(args) - with open(output_file.name, 'rb') as of: - with self._open('bootloader_signed.bin') as ef: + with open(output_file.name, "rb") as of: + with self._open("bootloader_signed.bin") as ef: self.assertEqual(ef.read(), of.read()) finally: os.unlink(output_file.name) def test_sign_v1_data(self): - self._test_sign_v1_data('ecdsa_secure_boot_signing_key.pem') + self._test_sign_v1_data("ecdsa_secure_boot_signing_key.pem") def test_sign_v1_data_pkcs8(self): - self._test_sign_v1_data('ecdsa_secure_boot_signing_key_pkcs8.pem') + self._test_sign_v1_data("ecdsa_secure_boot_signing_key_pkcs8.pem") def test_sign_v2_data(self): - signing_keys = ['rsa_secure_boot_signing_key.pem', - 'ecdsa192_secure_boot_signing_key.pem', - 'ecdsa_secure_boot_signing_key.pem'] + signing_keys = [ + "rsa_secure_boot_signing_key.pem", + "ecdsa192_secure_boot_signing_key.pem", + "ecdsa_secure_boot_signing_key.pem", + ] for key in signing_keys: with tempfile.NamedTemporaryFile() as output_file: - args = self.SignArgs('2', [self._open(key)], - output_file.name, False, - self._open('bootloader_unsigned_v2.bin')) + args = self.SignArgs( + "2", + [self._open(key)], + output_file.name, + False, + self._open("bootloader_unsigned_v2.bin"), + ) espsecure.sign_data(args) - args = self.VerifyArgs('2', self._open(key), - output_file) + args = self.VerifyArgs("2", self._open(key), output_file) espsecure.verify_signature(args) def test_sign_v2_multiple_keys(self): # 3 keys + Verify with 3rd key with tempfile.NamedTemporaryFile() as output_file: - args = self.SignArgs('2', [self._open('rsa_secure_boot_signing_key.pem'), - self._open('rsa_secure_boot_signing_key2.pem'), - self._open('rsa_secure_boot_signing_key3.pem')], - output_file.name, False, - self._open('bootloader_unsigned_v2.bin')) + args = self.SignArgs( + "2", + [ + self._open("rsa_secure_boot_signing_key.pem"), + self._open("rsa_secure_boot_signing_key2.pem"), + self._open("rsa_secure_boot_signing_key3.pem"), + ], + output_file.name, + False, + self._open("bootloader_unsigned_v2.bin"), + ) espsecure.sign_data(args) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key3.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key3.pem"), output_file + ) espsecure.verify_signature(args) output_file.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key2.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key2.pem"), output_file + ) espsecure.verify_signature(args) output_file.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key.pem"), output_file + ) espsecure.verify_signature(args) def test_sign_v2_append_signatures(self): - # Append signatures + Verify with an appended key (bootloader_signed_v2.bin already signed with rsa_secure_boot_signing_key.pem) + # Append signatures + Verify with an appended key + # (bootloader_signed_v2.bin already signed with rsa_secure_boot_signing_key.pem) with tempfile.NamedTemporaryFile() as output_file: - args = self.SignArgs('2', [self._open('rsa_secure_boot_signing_key2.pem'), - self._open('rsa_secure_boot_signing_key3.pem')], - output_file.name, True, - self._open('bootloader_signed_v2.bin')) + args = self.SignArgs( + "2", + [ + self._open("rsa_secure_boot_signing_key2.pem"), + self._open("rsa_secure_boot_signing_key3.pem"), + ], + output_file.name, + True, + self._open("bootloader_signed_v2.bin"), + ) espsecure.sign_data(args) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key.pem"), output_file + ) espsecure.verify_signature(args) output_file.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key2.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key2.pem"), output_file + ) espsecure.verify_signature(args) output_file.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key3.pem'), - output_file) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key3.pem"), output_file + ) espsecure.verify_signature(args) def test_sign_v2_append_signatures_multiple_steps(self): # similar to previous test, but sign in two invocations - with tempfile.NamedTemporaryFile() as output_file1, tempfile.NamedTemporaryFile() as output_file2: - args = self.SignArgs('2', [self._open('rsa_secure_boot_signing_key2.pem')], - output_file1.name, True, - self._open('bootloader_signed_v2.bin')) + with tempfile.NamedTemporaryFile() as output_file1, tempfile.NamedTemporaryFile() as output_file2: # noqa E501 + args = self.SignArgs( + "2", + [self._open("rsa_secure_boot_signing_key2.pem")], + output_file1.name, + True, + self._open("bootloader_signed_v2.bin"), + ) espsecure.sign_data(args) - args = self.SignArgs('2', [self._open('rsa_secure_boot_signing_key3.pem')], - output_file2.name, True, - output_file1) + args = self.SignArgs( + "2", + [self._open("rsa_secure_boot_signing_key3.pem")], + output_file2.name, + True, + output_file1, + ) espsecure.sign_data(args) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key.pem'), - output_file2) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key.pem"), output_file2 + ) espsecure.verify_signature(args) output_file2.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key2.pem'), - output_file2) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key2.pem"), output_file2 + ) espsecure.verify_signature(args) output_file2.seek(0) - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key3.pem'), - output_file2) + args = self.VerifyArgs( + "2", self._open("rsa_secure_boot_signing_key3.pem"), output_file2 + ) espsecure.verify_signature(args) def test_verify_signature_signing_key(self): # correct key v1 - args = self.VerifyArgs('1', self._open('ecdsa_secure_boot_signing_key.pem'), - self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", + self._open("ecdsa_secure_boot_signing_key.pem"), + self._open("bootloader_signed.bin"), + ) espsecure.verify_signature(args) # correct key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key.pem'), - self._open('bootloader_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_key.pem"), + self._open("bootloader_signed_v2.bin"), + ) espsecure.verify_signature(args) # correct key v2 (ecdsa256) - args = self.VerifyArgs('2', self._open('ecdsa_secure_boot_signing_key.pem'), - self._open('bootloader_signed_v2_ecdsa256.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa_secure_boot_signing_key.pem"), + self._open("bootloader_signed_v2_ecdsa256.bin"), + ) espsecure.verify_signature(args) # correct key v2 (ecdsa192) - args = self.VerifyArgs('2', self._open('ecdsa192_secure_boot_signing_key.pem'), - self._open('bootloader_signed_v2_ecdsa192.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa192_secure_boot_signing_key.pem"), + self._open("bootloader_signed_v2_ecdsa192.bin"), + ) espsecure.verify_signature(args) # wrong key v1 - args = self.VerifyArgs('1', self._open('ecdsa_secure_boot_signing_key2.pem'), - self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", + self._open("ecdsa_secure_boot_signing_key2.pem"), + self._open("bootloader_signed.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) self.assertIn("Signature is not valid", str(cm.exception)) # wrong key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key2.pem'), - self._open('bootloader_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_key2.pem"), + self._open("bootloader_signed_v2.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # right key, wrong scheme (ecdsa256, v2) - args = self.VerifyArgs('2', self._open('ecdsa_secure_boot_signing_key.pem'), - self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa_secure_boot_signing_key.pem"), + self._open("bootloader_signed.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) self.assertIn("Invalid datafile", str(cm.exception)) # wrong key v2 (ecdsa256) - args = self.VerifyArgs('2', self._open('ecdsa_secure_boot_signing_key2.pem'), - self._open('bootloader_signed_v2_ecdsa256.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa_secure_boot_signing_key2.pem"), + self._open("bootloader_signed_v2_ecdsa256.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # wrong key v2 (ecdsa192) - args = self.VerifyArgs('2', self._open('ecdsa192_secure_boot_signing_key2.pem'), - self._open('bootloader_signed_v2_ecdsa192.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa192_secure_boot_signing_key2.pem"), + self._open("bootloader_signed_v2_ecdsa192.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # multi-signed wrong key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_key4.pem'), - self._open('bootloader_multi_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_key4.pem"), + self._open("bootloader_multi_signed_v2.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) def test_verify_signature_public_key(self): # correct key v1 - args = self.VerifyArgs('1', self._open('ecdsa_secure_boot_signing_pubkey.pem'), - self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", + self._open("ecdsa_secure_boot_signing_pubkey.pem"), + self._open("bootloader_signed.bin"), + ) espsecure.verify_signature(args) # correct key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_pubkey.pem'), - self._open('bootloader_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_pubkey.pem"), + self._open("bootloader_signed_v2.bin"), + ) espsecure.verify_signature(args) # correct key v2 (ecdsa256) - args = self.VerifyArgs('2', self._open('ecdsa_secure_boot_signing_pubkey.pem'), - self._open('bootloader_signed_v2_ecdsa256.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa_secure_boot_signing_pubkey.pem"), + self._open("bootloader_signed_v2_ecdsa256.bin"), + ) espsecure.verify_signature(args) # correct key v2 (ecdsa192) - args = self.VerifyArgs('2', self._open('ecdsa192_secure_boot_signing_pubkey.pem'), - self._open('bootloader_signed_v2_ecdsa192.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa192_secure_boot_signing_pubkey.pem"), + self._open("bootloader_signed_v2_ecdsa192.bin"), + ) espsecure.verify_signature(args) # wrong key v1 - args = self.VerifyArgs('1', self._open('ecdsa_secure_boot_signing_pubkey2.pem'), - self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", + self._open("ecdsa_secure_boot_signing_pubkey2.pem"), + self._open("bootloader_signed.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) self.assertIn("Signature is not valid", str(cm.exception)) # wrong key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_pubkey2.pem'), - self._open('bootloader_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_pubkey2.pem"), + self._open("bootloader_signed_v2.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # wrong key v2 (ecdsa256) - args = self.VerifyArgs('2', self._open('ecdsa_secure_boot_signing_pubkey2.pem'), - self._open('bootloader_signed_v2_ecdsa256.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa_secure_boot_signing_pubkey2.pem"), + self._open("bootloader_signed_v2_ecdsa256.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # wrong key v2 (ecdsa192) - args = self.VerifyArgs('2', self._open('ecdsa192_secure_boot_signing_pubkey2.pem'), - self._open('bootloader_signed_v2_ecdsa192.bin')) + args = self.VerifyArgs( + "2", + self._open("ecdsa192_secure_boot_signing_pubkey2.pem"), + self._open("bootloader_signed_v2_ecdsa192.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) # multi-signed wrong key v2 - args = self.VerifyArgs('2', self._open('rsa_secure_boot_signing_pubkey4.pem'), - self._open('bootloader_multi_signed_v2.bin')) + args = self.VerifyArgs( + "2", + self._open("rsa_secure_boot_signing_pubkey4.pem"), + self._open("bootloader_multi_signed_v2.bin"), + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) - self.assertIn("Signature could not be verified with the provided key.", str(cm.exception)) + self.assertIn( + "Signature could not be verified with the provided key.", str(cm.exception) + ) def test_extract_binary_public_key(self): - with tempfile.NamedTemporaryFile() as pub_keyfile, tempfile.NamedTemporaryFile() as pub_keyfile2: - args = self.ExtractKeyArgs('1', self._open('ecdsa_secure_boot_signing_key.pem'), - pub_keyfile) + with tempfile.NamedTemporaryFile() as pub_keyfile, tempfile.NamedTemporaryFile() as pub_keyfile2: # noqa E501 + args = self.ExtractKeyArgs( + "1", self._open("ecdsa_secure_boot_signing_key.pem"), pub_keyfile + ) espsecure.extract_public_key(args) - args = self.ExtractKeyArgs('1', self._open('ecdsa_secure_boot_signing_key2.pem'), - pub_keyfile2) + args = self.ExtractKeyArgs( + "1", self._open("ecdsa_secure_boot_signing_key2.pem"), pub_keyfile2 + ) espsecure.extract_public_key(args) pub_keyfile.seek(0) pub_keyfile2.seek(0) # use correct extracted public key to verify - args = self.VerifyArgs('1', pub_keyfile, self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", pub_keyfile, self._open("bootloader_signed.bin") + ) espsecure.verify_signature(args) # use wrong extracted public key to try and verify - args = self.VerifyArgs('1', pub_keyfile2, self._open('bootloader_signed.bin')) + args = self.VerifyArgs( + "1", pub_keyfile2, self._open("bootloader_signed.bin") + ) with self.assertRaises(esptool.FatalError) as cm: espsecure.verify_signature(args) self.assertIn("Signature is not valid", str(cm.exception)) def test_generate_and_extract_key_v2(self): - keydir = tempfile.mkdtemp() # tempfile.TemporaryDirectory() would be better but we need compatibility with old Pythons + # tempfile.TemporaryDirectory() would be better + # but we need compatibility with old Pythons + keydir = tempfile.mkdtemp() self.addCleanup(os.rmdir, keydir) - # keyfile cannot exist before generation -> tempfile.NamedTemporaryFile() cannot be used for keyfile - keyfile_name = os.path.join(keydir, 'key.pem') + # keyfile cannot exist before generation -> tempfile.NamedTemporaryFile() + # cannot be used for keyfile + keyfile_name = os.path.join(keydir, "key.pem") - # We need to manually delete the keyfile as we are iterating over different schemes with the same keyfile - # so instead of using addCleanup, we remove it using os.remove at the end of each pass + # We need to manually delete the keyfile as we are iterating over + # different schemes with the same keyfile so instead of using addCleanup, + # we remove it using os.remove at the end of each pass for scheme in ["rsa3072", "ecdsa192", "ecdsa256"]: - args = self.GenerateKeyArgs('2', scheme, keyfile_name) + args = self.GenerateKeyArgs("2", scheme, keyfile_name) espsecure.generate_signing_key(args) - with tempfile.NamedTemporaryFile() as pub_keyfile, open(keyfile_name, 'rb') as keyfile: - args = self.ExtractKeyArgs('2', keyfile, pub_keyfile) + with tempfile.NamedTemporaryFile() as pub_keyfile, open( + keyfile_name, "rb" + ) as keyfile: + args = self.ExtractKeyArgs("2", keyfile, pub_keyfile) espsecure.extract_public_key(args) os.remove(keyfile_name) class FlashEncryptionTests(EspSecureTestCase): - EncryptArgs = namedtuple('encrypt_flash_data_args', - ['keyfile', - 'output', - 'address', - 'flash_crypt_conf', - 'aes_xts', - 'plaintext_file'] - ) + EncryptArgs = namedtuple( + "encrypt_flash_data_args", + [ + "keyfile", + "output", + "address", + "flash_crypt_conf", + "aes_xts", + "plaintext_file", + ], + ) - DecryptArgs = namedtuple('decrypt_flash_data_args', - ['keyfile', - 'output', - 'address', - 'flash_crypt_conf', - 'aes_xts', - 'encrypted_file'] - ) + DecryptArgs = namedtuple( + "decrypt_flash_data_args", + [ + "keyfile", + "output", + "address", + "flash_crypt_conf", + "aes_xts", + "encrypted_file", + ], + ) - def _test_encrypt_decrypt(self, input_plaintext, expected_ciphertext, key_path, offset, flash_crypt_conf=0xf, aes_xts=None): + def _test_encrypt_decrypt( + self, + input_plaintext, + expected_ciphertext, + key_path, + offset, + flash_crypt_conf=0xF, + aes_xts=None, + ): original_plaintext = self._open(input_plaintext) keyfile = self._open(key_path) ciphertext = io.BytesIO() - args = self.EncryptArgs(keyfile, - ciphertext, - offset, - flash_crypt_conf, - aes_xts, - original_plaintext) + args = self.EncryptArgs( + keyfile, ciphertext, offset, flash_crypt_conf, aes_xts, original_plaintext + ) espsecure.encrypt_flash_data(args) original_plaintext.seek(0) @@ -449,12 +574,9 @@ class FlashEncryptionTests(EspSecureTestCase): ciphertext.seek(0) keyfile.seek(0) plaintext = io.BytesIO() - args = self.DecryptArgs(keyfile, - plaintext, - offset, - flash_crypt_conf, - aes_xts, - ciphertext) + args = self.DecryptArgs( + keyfile, plaintext, offset, flash_crypt_conf, aes_xts, ciphertext + ) espsecure.decrypt_flash_data(args) original_plaintext.seek(0) @@ -462,82 +584,90 @@ class FlashEncryptionTests(EspSecureTestCase): class ESP32FlashEncryptionTests(FlashEncryptionTests): - def test_encrypt_decrypt_bootloader(self): - self._test_encrypt_decrypt('bootloader.bin', - 'bootloader-encrypted.bin', - '256bit_key.bin', - 0x1000, - 0xf) + self._test_encrypt_decrypt( + "bootloader.bin", "bootloader-encrypted.bin", "256bit_key.bin", 0x1000, 0xF + ) def test_encrypt_decrypt_app(self): - self._test_encrypt_decrypt('hello-world-signed.bin', - 'hello-world-signed-encrypted.bin', - 'ef-flashencryption-key.bin', - 0x20000, - 0xf) + self._test_encrypt_decrypt( + "hello-world-signed.bin", + "hello-world-signed-encrypted.bin", + "ef-flashencryption-key.bin", + 0x20000, + 0xF, + ) def test_encrypt_decrypt_non_default_conf(self): - """ Try some non-default (non-recommended) flash_crypt_conf settings """ - for conf in [0x0, 0x3, 0x9, 0xc]: - self._test_encrypt_decrypt('bootloader.bin', - 'bootloader-encrypted-conf%x.bin' % conf, - '256bit_key.bin', - 0x1000, - conf) + """Try some non-default (non-recommended) flash_crypt_conf settings""" + for conf in [0x0, 0x3, 0x9, 0xC]: + self._test_encrypt_decrypt( + "bootloader.bin", + "bootloader-encrypted-conf%x.bin" % conf, + "256bit_key.bin", + 0x1000, + conf, + ) class AesXtsFlashEncryptionTests(FlashEncryptionTests): - def test_encrypt_decrypt_bootloader(self): - self._test_encrypt_decrypt('bootloader.bin', - 'bootloader-encrypted-aes-xts.bin', - '256bit_key.bin', - 0x1000, aes_xts=True) + self._test_encrypt_decrypt( + "bootloader.bin", + "bootloader-encrypted-aes-xts.bin", + "256bit_key.bin", + 0x1000, + aes_xts=True, + ) def test_encrypt_decrypt_app(self): - self._test_encrypt_decrypt('hello-world-signed.bin', - 'hello-world-signed-encrypted-aes-xts.bin', - 'ef-flashencryption-key.bin', - 0x20000, aes_xts=True) + self._test_encrypt_decrypt( + "hello-world-signed.bin", + "hello-world-signed-encrypted-aes-xts.bin", + "ef-flashencryption-key.bin", + 0x20000, + aes_xts=True, + ) def test_encrypt_decrypt_app_512_bit_key(self): - self._test_encrypt_decrypt('hello-world-signed.bin', - 'hello-world-signed-encrypted-aes-xts-256.bin', - '512bit_key.bin', - 0x10000, aes_xts=True) + self._test_encrypt_decrypt( + "hello-world-signed.bin", + "hello-world-signed-encrypted-aes-xts-256.bin", + "512bit_key.bin", + 0x10000, + aes_xts=True, + ) def test_padding(self): # Random 2048 bits hex string - plaintext = binascii.unhexlify("c33b7c49f12a969a9bb45af5f660b73f" - "3b372685012da570df1cf99d1a82eabb" - "fdf6aa16b9675bd8a2f95e871513e175" - "3bc89f57986ecfb2707a3d3b59a46968" - "5e6609d2e9c21d4b2310571175e6e3de" - "2656ee22243f557b925ef39ff782ab56" - "f821e6859ee852000daae7c03a7c77ce" - "58744f15fbdf0ad4ae6e964aedd6316a" - "cf0e36935eef895cd14a60fe682fb971" - "eb239eae38b770bdf969017c9decfd91" - "b7c60329fb0c896684f0e7415f99dec1" - "da0572fac360a3e6d7219973a7de07e5" - "33b5abfdf5917ed5bfe54d660a6f5047" - "32fdb8d07259bfcdc67da87293857c11" - "427b2bae5f00da4a4b2b00b588ff5109" - "4c41f07f02f680f8826841b43da3f25b") + plaintext = binascii.unhexlify( + "c33b7c49f12a969a9bb45af5f660b73f" + "3b372685012da570df1cf99d1a82eabb" + "fdf6aa16b9675bd8a2f95e871513e175" + "3bc89f57986ecfb2707a3d3b59a46968" + "5e6609d2e9c21d4b2310571175e6e3de" + "2656ee22243f557b925ef39ff782ab56" + "f821e6859ee852000daae7c03a7c77ce" + "58744f15fbdf0ad4ae6e964aedd6316a" + "cf0e36935eef895cd14a60fe682fb971" + "eb239eae38b770bdf969017c9decfd91" + "b7c60329fb0c896684f0e7415f99dec1" + "da0572fac360a3e6d7219973a7de07e5" + "33b5abfdf5917ed5bfe54d660a6f5047" + "32fdb8d07259bfcdc67da87293857c11" + "427b2bae5f00da4a4b2b00b588ff5109" + "4c41f07f02f680f8826841b43da3f25b" + ) plaintext_file = io.BytesIO(plaintext) ciphertext_full_block = io.BytesIO() - keyfile = self._open('256bit_key.bin') + keyfile = self._open("256bit_key.bin") address = 0x1000 - encrypt_args_padded = self.EncryptArgs(keyfile, - ciphertext_full_block, - address, - None, - 'aes_xts', - plaintext_file) + encrypt_args_padded = self.EncryptArgs( + keyfile, ciphertext_full_block, address, None, "aes_xts", plaintext_file + ) espsecure.encrypt_flash_data(encrypt_args_padded) @@ -554,14 +684,16 @@ class AesXtsFlashEncryptionTests(FlashEncryptionTests): offset = b * i # encrypt the whole plaintext a substring of b bytes at a time - plaintext_sub = io.BytesIO(plaintext[offset:offset + b]) + plaintext_sub = io.BytesIO(plaintext[offset : offset + b]) - encrypt_args = self.EncryptArgs(keyfile, - ciphertext, - address + offset, - None, - 'aes_xts', - plaintext_sub) + encrypt_args = self.EncryptArgs( + keyfile, + ciphertext, + address + offset, + None, + "aes_xts", + plaintext_sub, + ) espsecure.encrypt_flash_data(encrypt_args) @@ -569,25 +701,38 @@ class AesXtsFlashEncryptionTests(FlashEncryptionTests): class DigestTests(EspSecureTestCase): - def test_digest_private_key(self): with tempfile.NamedTemporaryFile(delete=False) as f: self.addCleanup(os.remove, f.name) outfile_name = f.name - self.run_espsecure('digest_private_key --keyfile secure_images/ecdsa_secure_boot_signing_key.pem {}'.format(outfile_name)) + self.run_espsecure( + "digest_private_key " + "--keyfile secure_images/ecdsa_secure_boot_signing_key.pem " + "{}".format(outfile_name) + ) - with open(outfile_name, 'rb') as f: - self.assertEqual(f.read(), binascii.unhexlify('7b7b53708fc89d5e0b2df2571fb8f9d778f61a422ff1101a22159c4b34aad0aa')) + with open(outfile_name, "rb") as f: + self.assertEqual( + f.read(), + binascii.unhexlify( + "7b7b53708fc89d5e0b2df2571fb8f9d778f61a422ff1101a22159c4b34aad0aa" + ), + ) def test_digest_private_key_with_invalid_output(self): - fname = 'secure_images/ecdsa_secure_boot_signing_key.pem' + fname = "secure_images/ecdsa_secure_boot_signing_key.pem" with self.assertRaises(subprocess.CalledProcessError): - self.run_espsecure('digest_private_key --keyfile {} {}'.format(fname, fname)) + self.run_espsecure( + "digest_private_key --keyfile {} {}".format(fname, fname) + ) -if __name__ == '__main__': +if __name__ == "__main__": print("Running espsecure tests...") - print("Using espsecure %s at %s" % (esptool.__version__, os.path.abspath(espsecure.__file__))) + print( + "Using espsecure %s at %s" + % (esptool.__version__, os.path.abspath(espsecure.__file__)) + ) unittest.main(buffer=True) diff --git a/test/test_esptool.py b/test/test_esptool.py index 9041218..66aca47 100755 --- a/test/test_esptool.py +++ b/test/test_esptool.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """ -esptool.py "unit" tests (really integration tests). Uses a device connected to the serial port. +esptool.py "unit" tests (really integration tests). +Uses a device connected to the serial port. WILL MESS UP THE DEVICE'S SPI FLASH CONTENTS @@ -24,7 +25,7 @@ import unittest from socket import AF_INET, SOCK_STREAM, socket from time import sleep -sys.path.append('..') +sys.path.append("..") import espefuse import esptool @@ -55,8 +56,12 @@ def with_retry(run_test_func): def run_with_retry(*args, **kwargs): attempts = 5 result = args[1] - failuresBefore = len(result.failures) # Check how many tests are marked as failed before starting - errorsBefore = len(result.errors) # Check how many tests are marked as error before starting + failuresBefore = len( + result.failures + ) # Check how many tests are marked as failed before starting + errorsBefore = len( + result.errors + ) # Check how many tests are marked as error before starting for i in range(1, attempts + 1): run_test_func(*args, **kwargs) # Run the test @@ -67,8 +72,11 @@ def with_retry(run_test_func): if i == attempts: print("\nTest failed after {} attempts".format(attempts)) break - result.failures.pop() if failure else result.errors.pop() # Remove last result - print("\nTest failed, retrying with {} attempts left".format(attempts - i)) + # Remove last result + result.failures.pop() if failure else result.errors.pop() + print( + "\nTest failed, retrying with {} attempts left".format(attempts - i) + ) else: break # Test passed @@ -87,13 +95,18 @@ RETURN_CODE_FATAL_ERROR = 2 class ESPRFC2217Server(object): - """ Creates a virtual serial port accessible through rfc2217 port. - """ + """Creates a virtual serial port accessible through rfc2217 port.""" def __init__(self, rfc2217_port=None): self.port = rfc2217_port or self.get_free_port() - self.cmd = [sys.executable, ESPRFC2217SERVER_PY, '-p', str(self.port), serialport] - self.server_output_file = open(str(chip) + "_server.out", 'a') + self.cmd = [ + sys.executable, + ESPRFC2217SERVER_PY, + "-p", + str(self.port), + serialport, + ] + self.server_output_file = open(str(chip) + "_server.out", "a") self.server_output_file.write("************************************") self.p = None self.wait_for_server_starts(attempts_count=5) @@ -101,7 +114,7 @@ class ESPRFC2217Server(object): @staticmethod def get_free_port(): s = socket(AF_INET, SOCK_STREAM) - s.bind(('', 0)) + s.bind(("", 0)) port = s.getsockname()[1] s.close() return port @@ -109,18 +122,26 @@ class ESPRFC2217Server(object): def wait_for_server_starts(self, attempts_count): for attempt in range(attempts_count): try: - self.p = subprocess.Popen(self.cmd, cwd=TEST_DIR, stdout=self.server_output_file, - stderr=subprocess.STDOUT, close_fds=True) + self.p = subprocess.Popen( + self.cmd, + cwd=TEST_DIR, + stdout=self.server_output_file, + stderr=subprocess.STDOUT, + close_fds=True, + ) sleep(2) s = socket(AF_INET, SOCK_STREAM) - result = s.connect_ex(('localhost', self.port)) + result = s.connect_ex(("localhost", self.port)) s.close() if result == 0: print("Server started successfully.") return except Exception as e: print(e) - print("Server start failed." + (" Retrying . . ." if attempt < attempts_count - 1 else "")) + print( + "Server start failed." + + (" Retrying . . ." if attempt < attempts_count - 1 else "") + ) self.p.terminate() raise Exception("Server not started successfully!") @@ -133,13 +154,14 @@ class ESPRFC2217Server(object): class EsptoolTestCase(unittest.TestCase): - def run_espsecure(self, args): cmd = [sys.executable, ESPSECURE_PY] + args.split(" ") print("Running %s..." % (" ".join(cmd))) try: - output = subprocess.check_output([str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT) + output = subprocess.check_output( + [str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT + ) print(output) # for more complete stdout logs on failure return output.decode("utf-8") except subprocess.CalledProcessError as e: @@ -147,12 +169,15 @@ class EsptoolTestCase(unittest.TestCase): raise e def run_esptool(self, args, baud=None, chip_name=chip, rfc2217_port=None): - """ Run esptool with the specified arguments. --chip, --port and --baud - are filled in automatically from the command line. (can override default baud rate with baud param.) + """ + Run esptool with the specified arguments. --chip, --port and --baud + are filled in automatically from the command line. + (can override default baud rate with baud param.) Additional args passed in args parameter as a string. - Returns output from esptool.py as a string if there is any. Raises an exception if esptool.py fails. + Returns output from esptool.py as a string if there is any. + Raises an exception if esptool.py fails. """ if baud is None: baud = default_baudrate @@ -160,10 +185,13 @@ class EsptoolTestCase(unittest.TestCase): cmd = [sys.executable, ESPTOOL_PY] + trace_args if chip_name: cmd += ["--chip", chip] - cmd += ["--port", rfc2217_port or serialport, "--baud", str(baud)] + args.split(" ") + cmd += ["--port", rfc2217_port or serialport, "--baud", str(baud)] + cmd += args.split(" ") print("Running %s..." % (" ".join(cmd))) try: - output = subprocess.check_output([str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT) + output = subprocess.check_output( + [str(s) for s in cmd], cwd=TEST_DIR, stderr=subprocess.STDOUT + ) print(output) # for more complete stdout logs on failure return output.decode("utf-8") except subprocess.CalledProcessError as e: @@ -171,8 +199,8 @@ class EsptoolTestCase(unittest.TestCase): raise e def run_esptool_error(self, args, baud=None): - """ Run esptool.py similar to run_esptool, but expect an - error. + """ + Run esptool.py similar to run_esptool, but expect an error. Verifies the error is an expected error not an unhandled exception, and returns the output from esptool.py as a string. @@ -187,14 +215,23 @@ class EsptoolTestCase(unittest.TestCase): print(50 * "*") def readback(self, offset, length): - """ Read contents of flash back, return to caller. """ - with tempfile.NamedTemporaryFile(delete=False) as tf: # need a file we can read into + """Read contents of flash back, return to caller.""" + with tempfile.NamedTemporaryFile( + delete=False + ) as tf: # need a file we can read into self.addCleanup(os.remove, tf.name) - self.run_esptool("--before default_reset read_flash %d %d %s" % (offset, length, tf.name)) + self.run_esptool( + "--before default_reset read_flash %d %d %s" % (offset, length, tf.name) + ) with open(tf.name, "rb") as f: rb = f.read() - self.assertEqual(length, len(rb), "read_flash length %d offset 0x%x yielded %d bytes!" % (length, offset, len(rb))) + self.assertEqual( + length, + len(rb), + "read_flash length %d offset 0x%x yielded %d bytes!" + % (length, offset, len(rb)), + ) return rb def verify_readback(self, offset, length, compare_to, is_bootloader=False): @@ -202,7 +239,10 @@ class EsptoolTestCase(unittest.TestCase): with open(compare_to, "rb") as f: ct = f.read() if len(rb) != len(ct): - print("WARNING: Expected length %d doesn't match comparison %d" % (len(ct), len(rb))) + print( + "WARNING: Expected length %d doesn't match comparison %d" + % (len(ct), len(rb)) + ) print("Readback %d bytes" % len(rb)) if is_bootloader: # writing a bootloader image to bootloader offset can set flash size/etc, @@ -212,12 +252,14 @@ class EsptoolTestCase(unittest.TestCase): ct = ct[8:] for rb_b, ct_b, offs in zip(rb, ct, range(len(rb))): if rb_b != ct_b: - self.fail("First difference at offset 0x%x Expected %r got %r" % (offs, ct_b, rb_b)) + self.fail( + "First difference at offset 0x%x Expected %r got %r" + % (offs, ct_b, rb_b) + ) -@unittest.skipUnless(chip == 'esp32', 'ESP32 only') +@unittest.skipUnless(chip == "esp32", "ESP32 only") class TestFlashEncryption(EsptoolTestCase): - def valid_key_present(self): esp = esptool.ESP32ROM(serialport) esp.connect() @@ -225,33 +267,52 @@ class TestFlashEncryption(EsptoolTestCase): blk1_rd_en = efuses["BLOCK1"].is_readable() return not blk1_rd_en - """ since flash crypt config is not set correct this test should abort write """ - def test_blank_efuse_encrypt_write_abort(self): - print('test_blank_efuse_encrypt_write_abort') + """ + since flash crypt config is not set correctly, this test should abort write + """ + print("test_blank_efuse_encrypt_write_abort") if self.valid_key_present() is True: - raise unittest.SkipTest("Valid encryption key already programmed, aborting the test") + raise unittest.SkipTest( + "Valid encryption key already programmed, aborting the test" + ) - self.run_esptool("write_flash 0x1000 images/bootloader_esp32.bin 0x8000 images/partitions_singleapp.bin " - "0x10000 images/ram_helloworld/helloworld-esp32.bin") - output = self.run_esptool_error("write_flash --encrypt 0x10000 images/ram_helloworld/helloworld-esp32.bin") + self.run_esptool( + "write_flash 0x1000 images/bootloader_esp32.bin " + "0x8000 images/partitions_singleapp.bin " + "0x10000 images/ram_helloworld/helloworld-esp32.bin" + ) + output = self.run_esptool_error( + "write_flash --encrypt 0x10000 images/ram_helloworld/helloworld-esp32.bin" + ) self.assertIn("Flash encryption key is not programmed".lower(), output.lower()) - """ since ignore option is specified write should happen even though flash crypt config is 0 - later encrypted flash contents should be read back & compared with precomputed ciphertext - pass case """ - def test_blank_efuse_encrypt_write_continue1(self): - print('test_blank_efuse_encrypt_write_continue1') + """ + since ignore option is specified, write should happen even though flash crypt + config is 0 + later encrypted flash contents should be read back & compared with + precomputed ciphertext + pass test + """ + print("test_blank_efuse_encrypt_write_continue1") if self.valid_key_present() is True: - raise unittest.SkipTest("Valid encryption key already programmed, aborting the test") + raise unittest.SkipTest( + "Valid encryption key already programmed, aborting the test" + ) - self.run_esptool("write_flash --encrypt --ignore-flash-encryption-efuse-setting 0x10000 images/ram_helloworld/helloworld-esp32.bin") + self.run_esptool( + "write_flash --encrypt --ignore-flash-encryption-efuse-setting " + "0x10000 images/ram_helloworld/helloworld-esp32.bin" + ) self.run_esptool("read_flash 0x10000 192 images/read_encrypted_flash.bin") - self.run_espsecure("encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " - "--flash_crypt_conf 0 --output images/local_enc.bin images/ram_helloworld/helloworld-esp32.bin") + self.run_espsecure( + "encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " + "--flash_crypt_conf 0 --output images/local_enc.bin " + "images/ram_helloworld/helloworld-esp32.bin" + ) try: with open("images/read_encrypted_flash.bin", "rb") as file1: @@ -261,27 +322,43 @@ class TestFlashEncryption(EsptoolTestCase): read_file2 = file2.read() for rf1, rf2, i in zip(read_file1, read_file2, range(len(read_file2))): - self.assertEqual(rf1, rf2, "encrypted write failed: file mismatch at byte position %d" % i) + self.assertEqual( + rf1, + rf2, + "encrypted write failed: file mismatch at byte position %d" % i, + ) - print('encrypted write success') + print("encrypted write success") finally: os.remove("images/read_encrypted_flash.bin") os.remove("images/local_enc.bin") - """ since ignore option is specified write should happen even though flash crypt config is 0 - later encrypted flash contents should be read back & compared with precomputed ciphertext - fail case """ @unittest.expectedFailure def test_blank_efuse_encrypt_write_continue2(self): - print('test_blank_efuse_encrypt_write_continue2') + """ + since ignore option is specified, write should happen even though flash crypt + config is 0 + later encrypted flash contents should be read back & compared with + precomputed ciphertext + fail test + """ + print("test_blank_efuse_encrypt_write_continue2") if self.valid_key_present() is True: - raise unittest.SkipTest("Valid encryption key already programmed, aborting the test") + raise unittest.SkipTest( + "Valid encryption key already programmed, aborting the test" + ) - self.run_esptool("write_flash --encrypt --ignore-flash-encryption-efuse-setting 0x10000 images/ram_helloworld/helloworld-esp32_edit.bin") + self.run_esptool( + "write_flash --encrypt --ignore-flash-encryption-efuse-setting " + "0x10000 images/ram_helloworld/helloworld-esp32_edit.bin" + ) self.run_esptool("read_flash 0x10000 192 images/read_encrypted_flash.bin") - self.run_espsecure("encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " - "--flash_crypt_conf 0 --output images/local_enc.bin images/ram_helloworld/helloworld-esp32.bin") + self.run_espsecure( + "encrypt_flash_data --address 0x10000 --keyfile images/aes_key.bin " + "--flash_crypt_conf 0 --output images/local_enc.bin " + "images/ram_helloworld/helloworld-esp32.bin" + ) try: with open("images/read_encrypted_flash.bin", "rb") as file1: @@ -299,7 +376,6 @@ class TestFlashEncryption(EsptoolTestCase): class TestFlashing(EsptoolTestCase): - def test_short_flash(self): self.run_esptool("write_flash 0x0 images/one_kb.bin") self.verify_readback(0, 1024, "images/one_kb.bin") @@ -322,7 +398,7 @@ class TestFlashing(EsptoolTestCase): self.verify_readback(0, 4096, "images/sector.bin") def test_correct_offset(self): - """ Verify writing at an offset actually writes to that offset. """ + """Verify writing at an offset actually writes to that offset.""" self.run_esptool("write_flash 0x2000 images/sector.bin") time.sleep(0.1) three_sectors = self.readback(0, 0x3000) @@ -332,13 +408,17 @@ class TestFlashing(EsptoolTestCase): self.assertEqual(last_sector, ct) def test_no_compression_flash(self): - self.run_esptool("write_flash -u 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") + self.run_esptool( + "write_flash -u 0x0 images/sector.bin 0x1000 images/fifty_kb.bin" + ) self.verify_readback(0, 4096, "images/sector.bin") self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") - @unittest.skipUnless(chip != 'esp8266', 'Added in ESP32') + @unittest.skipUnless(chip != "esp8266", "Added in ESP32") def test_compressed_nostub_flash(self): - self.run_esptool("--no-stub write_flash -z 0x0 images/sector.bin 0x1000 images/fifty_kb.bin") + self.run_esptool( + "--no-stub write_flash -z 0x0 images/sector.bin 0x1000 images/fifty_kb.bin" + ) self.verify_readback(0, 4096, "images/sector.bin") self.verify_readback(4096, 50 * 1024, "images/fifty_kb.bin") @@ -371,27 +451,36 @@ class TestFlashing(EsptoolTestCase): self.run_esptool("write_flash -u 0x0 images/%s" % NODEMCU_FILE) def test_write_overlap(self): - output = self.run_esptool_error("write_flash 0x0 images/bootloader_esp32.bin 0x1000 images/one_kb.bin") + output = self.run_esptool_error( + "write_flash 0x0 images/bootloader_esp32.bin 0x1000 images/one_kb.bin" + ) self.assertIn("Detected overlap at address: 0x1000 ", output) def test_repeated_address(self): - output = self.run_esptool_error("write_flash 0x0 images/one_kb.bin 0x0 images/one_kb.bin") + output = self.run_esptool_error( + "write_flash 0x0 images/one_kb.bin 0x0 images/one_kb.bin" + ) self.assertIn("Detected overlap at address: 0x0 ", output) def test_write_sector_overlap(self): - # These two 1KB files don't overlap, but they do both touch sector at 0x1000 so should fail - output = self.run_esptool_error("write_flash 0xd00 images/one_kb.bin 0x1d00 images/one_kb.bin") + # These two 1KB files don't overlap, + # but they do both touch sector at 0x1000 so should fail + output = self.run_esptool_error( + "write_flash 0xd00 images/one_kb.bin 0x1d00 images/one_kb.bin" + ) self.assertIn("Detected overlap at address: 0x1d00", output) def test_write_no_overlap(self): - output = self.run_esptool("write_flash 0x0 images/bootloader_esp32.bin 0x2000 images/one_kb.bin") + output = self.run_esptool( + "write_flash 0x0 images/bootloader_esp32.bin 0x2000 images/one_kb.bin" + ) self.assertNotIn("Detected overlap at address", output) def test_compressible_file(self): with tempfile.NamedTemporaryFile(delete=False) as f: self.addCleanup(os.remove, f.name) file_size = 1024 * 1024 - f.write(b'\x00' * file_size) + f.write(b"\x00" * file_size) self.run_esptool("write_flash 0x10000 {}".format(f.name)) def test_compressible_non_trivial_file(self): @@ -400,12 +489,14 @@ class TestFlashing(EsptoolTestCase): file_size = 1000 * 1000 same_bytes = 8000 for _ in range(file_size // same_bytes): - f.write(struct.pack('B', random.randrange(0, 1 << 8)) * same_bytes) + f.write(struct.pack("B", random.randrange(0, 1 << 8)) * same_bytes) self.run_esptool("write_flash 0x10000 {}".format(f.name)) def test_zero_length(self): # Zero length files are skipped with a warning - output = self.run_esptool("write_flash 0x10000 images/one_kb.bin 0x11000 images/zerolength.bin") + output = self.run_esptool( + "write_flash 0x10000 images/one_kb.bin 0x11000 images/zerolength.bin" + ) self.verify_readback(0x10000, 1024, "images/one_kb.bin") self.assertIn("zerolength.bin is empty", output) @@ -414,15 +505,19 @@ class TestFlashing(EsptoolTestCase): self.verify_readback(0x0, 1, "images/onebyte.bin") def test_erase_range_messages(self): - output = self.run_esptool("write_flash 0x1000 images/bootloader_esp32.bin 0x0FC00 images/one_kb.bin") + output = self.run_esptool( + "write_flash 0x1000 images/bootloader_esp32.bin 0x0FC00 images/one_kb.bin" + ) self.assertIn("Flash will be erased from 0x00001000 to 0x00002fff...", output) - self.assertIn("WARNING: Flash address 0x0000fc00 is not aligned to a 0x1000 byte flash sector." - " 0xc00 bytes before this address will be erased.", output) + self.assertIn( + "WARNING: Flash address 0x0000fc00 is not aligned to a 0x1000 " + "byte flash sector. 0xc00 bytes before this address will be erased.", + output, + ) self.assertIn("Flash will be erased from 0x0000f000 to 0x0000ffff...", output) class TestFlashSizes(EsptoolTestCase): - def test_high_offset(self): self.run_esptool("write_flash -fs 4MB 0x300000 images/one_kb.bin") self.verify_readback(0x300000, 1024, "images/one_kb.bin") @@ -443,12 +538,16 @@ class TestFlashSizes(EsptoolTestCase): self.run_esptool_error("write_flash -fs 10MB 0x6000 images/one_kb.bin") def test_write_past_end_fails(self): - output = self.run_esptool_error("write_flash -fs 1MB 0x280000 images/one_kb.bin") + output = self.run_esptool_error( + "write_flash -fs 1MB 0x280000 images/one_kb.bin" + ) self.assertIn("File images/one_kb.bin", output) self.assertIn("will not fit", output) def test_write_no_compression_past_end_fails(self): - output = self.run_esptool_error("write_flash -u -fs 1MB 0x280000 images/one_kb.bin") + output = self.run_esptool_error( + "write_flash -u -fs 1MB 0x280000 images/one_kb.bin" + ) self.assertIn("File images/one_kb.bin", output) self.assertIn("will not fit", output) @@ -488,7 +587,7 @@ class TestFlashSizes(EsptoolTestCase): class TestFlashDetection(EsptoolTestCase): def test_flash_id(self): - """ Test manufacturer and device response of flash detection. """ + """Test manufacturer and device response of flash detection.""" res = self.run_esptool("flash_id") self.assertTrue("Manufacturer:" in res) self.assertTrue("Device:" in res) @@ -496,19 +595,25 @@ class TestFlashDetection(EsptoolTestCase): class TestStubReuse(EsptoolTestCase): def test_stub_reuse_with_synchronization(self): - """ Keep the flasher stub running and reuse it the next time. """ - res = self.run_esptool("--after no_reset_stub flash_id") # flasher stub keeps running after this + """Keep the flasher stub running and reuse it the next time.""" + res = self.run_esptool( + "--after no_reset_stub flash_id" + ) # flasher stub keeps running after this self.assertTrue("Manufacturer:" in res) - res = self.run_esptool("--before no_reset flash_id") # do sync before (without reset it talks to the flasher stub) + res = self.run_esptool( + "--before no_reset flash_id" + ) # do sync before (without reset it talks to the flasher stub) self.assertTrue("Manufacturer:" in res) - @unittest.skipUnless(chip == 'esp8266', 'ESP8266 only') + @unittest.skipUnless(chip == "esp8266", "ESP8266 only") def test_stub_reuse_without_synchronization(self): """ - Keep the flasher stub running and reuse it the next time without synchronization. + Keep the flasher stub running and reuse it the next time + without synchronization. - Synchronization is necessary for chips where the ROM bootloader has different status length in comparison to - the flasher stub. Therefore, this is ESP8266 only test. + Synchronization is necessary for chips where the ROM bootloader has different + status length in comparison to the flasher stub. + Therefore, this is ESP8266 only test. """ res = self.run_esptool("--after no_reset_stub flash_id") self.assertTrue("Manufacturer:" in res) @@ -517,13 +622,12 @@ class TestStubReuse(EsptoolTestCase): class TestErase(EsptoolTestCase): - def test_chip_erase(self): self.run_esptool("write_flash 0x10000 images/one_kb.bin") self.verify_readback(0x10000, 0x400, "images/one_kb.bin") self.run_esptool("erase_flash") empty = self.readback(0x10000, 0x400) - self.assertTrue(empty == b'\xFF' * 0x400) + self.assertTrue(empty == b"\xFF" * 0x400) def test_region_erase(self): self.run_esptool("write_flash 0x10000 images/one_kb.bin") @@ -534,7 +638,7 @@ class TestErase(EsptoolTestCase): self.run_esptool("erase_region 0x10000 0x1000") self.verify_readback(0x11000, 0x1000, "images/sector.bin") empty = self.readback(0x10000, 0x1000) - self.assertTrue(empty == b'\xFF' * 0x1000) + self.assertTrue(empty == b"\xFF" * 0x1000) def test_large_region_erase(self): # verifies that erasing a large region doesn't time out @@ -542,7 +646,6 @@ class TestErase(EsptoolTestCase): class TestSectorBoundaries(EsptoolTestCase): - def test_end_sector(self): self.run_esptool("write_flash 0x10000 images/sector.bin") self.run_esptool("write_flash 0x0FC00 images/one_kb.bin") @@ -561,14 +664,15 @@ class TestSectorBoundaries(EsptoolTestCase): class TestVerifyCommand(EsptoolTestCase): - def test_verify_success(self): self.run_esptool("write_flash 0x5000 images/one_kb.bin") self.run_esptool("verify_flash 0x5000 images/one_kb.bin") def test_verify_failure(self): self.run_esptool("write_flash 0x6000 images/sector.bin") - output = self.run_esptool_error("verify_flash --diff=yes 0x6000 images/one_kb.bin") + output = self.run_esptool_error( + "verify_flash --diff=yes 0x6000 images/one_kb.bin" + ) self.assertIn("verify FAILED", output) self.assertIn("first @ 0x00006000", output) @@ -578,7 +682,6 @@ class TestVerifyCommand(EsptoolTestCase): class TestReadIdentityValues(EsptoolTestCase): - def test_read_mac(self): output = self.run_esptool("read_mac") mac = re.search(r"[0-9a-f:]{17}", output) @@ -587,7 +690,7 @@ class TestReadIdentityValues(EsptoolTestCase): self.assertNotEqual("00:00:00:00:00:00", mac) self.assertNotEqual("ff:ff:ff:ff:ff:ff", mac) - @unittest.skipUnless(chip == 'esp8266', 'ESP8266 only') + @unittest.skipUnless(chip == "esp8266", "ESP8266 only") def test_read_chip_id(self): output = self.run_esptool("chip_id") idstr = re.search("Chip ID: 0x([0-9a-f]+)", output) @@ -598,7 +701,6 @@ class TestReadIdentityValues(EsptoolTestCase): class TestMemoryOperations(EsptoolTestCase): - def test_memory_dump(self): output = self.run_esptool("dump_mem 0x50000000 128 memout.bin") self.assertIn("Read 128 bytes", output) @@ -616,7 +718,7 @@ class TestMemoryOperations(EsptoolTestCase): class TestKeepImageSettings(EsptoolTestCase): - """ Tests for the -fm keep, -ff keep options for write_flash """ + """Tests for the -fm keep, -ff keep options for write_flash""" def setUp(self): super(TestKeepImageSettings, self).setUp() @@ -628,60 +730,88 @@ class TestKeepImageSettings(EsptoolTestCase): "esp32s3": "images/bootloader_esp32s3.bin", "esp32c3": "images/bootloader_esp32c3.bin", }[chip] - self.flash_offset = 0x1000 if chip in ("esp32", "esp32s2") else 0 # bootloader offset + self.flash_offset = ( + 0x1000 if chip in ("esp32", "esp32s2") else 0 + ) # bootloader offset with open(self.BL_IMAGE, "rb") as f: self.header = f.read(8) def test_keep_does_not_change_settings(self): # defaults should all be keep - self.run_esptool("write_flash -fs keep 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "write_flash -fs keep 0x%x %s" % (self.flash_offset, self.BL_IMAGE) + ) self.verify_readback(self.flash_offset, 8, self.BL_IMAGE, False) # can also explicitly set all options - self.run_esptool("write_flash -fm keep -ff keep -fs keep 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "write_flash -fm keep -ff keep -fs keep 0x%x %s" + % (self.flash_offset, self.BL_IMAGE) + ) self.verify_readback(self.flash_offset, 8, self.BL_IMAGE, False) # verify_flash should also use 'keep' - self.run_esptool("verify_flash -fs keep 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "verify_flash -fs keep 0x%x %s" % (self.flash_offset, self.BL_IMAGE) + ) def test_detect_size_changes_size(self): - self.run_esptool("write_flash -fs detect 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "write_flash -fs detect 0x%x %s" % (self.flash_offset, self.BL_IMAGE) + ) readback = self.readback(self.flash_offset, 8) self.assertEqual(self.header[:3], readback[:3]) # first 3 bytes unchanged self.assertNotEqual(self.header[3], readback[3]) # size_freq byte changed self.assertEqual(self.header[4:], readback[4:]) # rest unchanged def test_explicit_set_size_freq_mode(self): - self.run_esptool("write_flash -fs 2MB -fm dout -ff 80m 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "write_flash -fs 2MB -fm dout -ff 80m 0x%x %s" + % (self.flash_offset, self.BL_IMAGE) + ) def val(x): try: return ord(x) # converts character to integer on Python 2 except TypeError: - return x # throws TypeError on Python 3 where x is already an integer + return x # throws TypeError on Python 3 where x is already an integer readback = self.readback(self.flash_offset, 8) self.assertEqual(self.header[0], readback[0]) self.assertEqual(self.header[1], readback[1]) - self.assertEqual(0x3f if chip == "esp8266" else 0x1f, val(readback[3])) # size_freq + self.assertEqual( + 0x3F if chip == "esp8266" else 0x1F, val(readback[3]) + ) # size_freq self.assertNotEqual(3, val(self.header[2])) # original image not dout mode self.assertEqual(3, val(readback[2])) # value in flash is dout mode - self.assertNotEqual(self.header[3], readback[3]) # size/freq values have changed - self.assertEqual(self.header[4:], readback[4:]) # entrypoint address hasn't changed + self.assertNotEqual( + self.header[3], readback[3] + ) # size/freq values have changed + self.assertEqual( + self.header[4:], readback[4:] + ) # entrypoint address hasn't changed # verify_flash should pass if we match params, fail otherwise - self.run_esptool("verify_flash -fs 2MB -fm dout -ff 80m 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) - self.run_esptool_error("verify_flash 0x%x %s" % (self.flash_offset, self.BL_IMAGE)) + self.run_esptool( + "verify_flash -fs 2MB -fm dout -ff 80m 0x%x %s" + % (self.flash_offset, self.BL_IMAGE) + ) + self.run_esptool_error( + "verify_flash 0x%x %s" % (self.flash_offset, self.BL_IMAGE) + ) class TestLoadRAM(EsptoolTestCase): - # flashing an application not supporting USB CDC will make /dev/ttyACM0 disappear and USB CDC tests will not work anymore + # flashing an application not supporting USB CDC will make + # /dev/ttyACM0 disappear and USB CDC tests will not work anymore @unittest.skipIf(chip == "esp32s2", "Not supported because of USB CDC mode") - @unittest.skipIf(chip == "esp32s3beta2", "TODO: write a IRAM test binary for esp32s3beta2") + @unittest.skipIf( + chip == "esp32s3beta2", "TODO: write a IRAM test binary for esp32s3beta2" + ) @unittest.skipIf(chip == "esp32s3", "TODO: write a IRAM test binary for esp32s3") @unittest.skipIf(chip == "esp32c3", "TODO: write a IRAM test binary for esp32c3") def test_load_ram(self): - """ Verify load_ram command + """Verify load_ram command The "hello world" binary programs for each chip print "Hello world!\n" to the serial port. @@ -696,17 +826,18 @@ class TestLoadRAM(EsptoolTestCase): class TestDeepSleepFlash(EsptoolTestCase): - - @unittest.skipUnless(chip == 'esp8266', 'ESP8266 only') + @unittest.skipUnless(chip == "esp8266", "ESP8266 only") def test_deep_sleep_flash(self): - """ Regression test for https://github.com/espressif/esptool/issues/351 + """Regression test for https://github.com/espressif/esptool/issues/351 - ESP8266 deep sleep can disable SPI flash chip, stub loader (or ROM loader) needs to re-enable it. + ESP8266 deep sleep can disable SPI flash chip, + stub loader (or ROM loader) needs to re-enable it. - NOTE: If this test fails, the ESP8266 may need a hard power cycle (probably with GPIO0 held LOW) - to recover. + NOTE: If this test fails, the ESP8266 may need a hard power cycle + (probably with GPIO0 held LOW) to recover. """ - # not even necessary to wake successfully from sleep, going into deep sleep is enough + # not even necessary to wake successfully from sleep, + # going into deep sleep is enough # (so GPIO16, etc, config is not important for this test) self.run_esptool("write_flash 0x0 images/esp8266_deepsleep.bin", baud=230400) @@ -720,22 +851,27 @@ class TestBootloaderHeaderRewriteCases(EsptoolTestCase): BL_OFFSET = 0x1000 if chip in ("esp32", "esp32s2") else 0 def test_flash_header_rewrite(self): - bl_image = {"esp8266": "images/esp8266_sdk/boot_v1.4(b1).bin", - "esp32": "images/bootloader_esp32.bin", - "esp32s2": "images/bootloader_esp32s2.bin", - "esp32s3beta2": "images/bootloader_esp32s3beta2.bin", - "esp32s3": "images/bootloader_esp32s3.bin", - "esp32c3": "images/bootloader_esp32c3.bin", - }[chip] + bl_image = { + "esp8266": "images/esp8266_sdk/boot_v1.4(b1).bin", + "esp32": "images/bootloader_esp32.bin", + "esp32s2": "images/bootloader_esp32s2.bin", + "esp32s3beta2": "images/bootloader_esp32s3beta2.bin", + "esp32s3": "images/bootloader_esp32s3.bin", + "esp32c3": "images/bootloader_esp32c3.bin", + }[chip] - output = self.run_esptool("write_flash -fm dout -ff 20m 0x%x %s" % (self.BL_OFFSET, bl_image)) + output = self.run_esptool( + "write_flash -fm dout -ff 20m 0x%x %s" % (self.BL_OFFSET, bl_image) + ) self.assertIn("Flash params set to", output) def test_flash_header_no_magic_no_rewrite(self): # first image doesn't start with magic byte, second image does # but neither are valid bootloader binary images for either chip for image in ["images/one_kb.bin", "images/one_kb_all_ef.bin"]: - output = self.run_esptool("write_flash -fm dout -ff 20m 0x%x %s" % (self.BL_OFFSET, image)) + output = self.run_esptool( + "write_flash -fm dout -ff 20m 0x%x %s" % (self.BL_OFFSET, image) + ) self.assertIn("not changing any flash settings", output) self.verify_readback(self.BL_OFFSET, 1024, image) @@ -765,14 +901,25 @@ class TestVirtualPort(TestAutoDetect): def test_auto_detect_virtual_port(self): with ESPRFC2217Server() as server: - output = self.run_esptool("chip_id", chip_name=None, - rfc2217_port='rfc2217://localhost:' + str(server.port) + '?ign_set_control') + output = self.run_esptool( + "chip_id", + chip_name=None, + rfc2217_port="rfc2217://localhost:" + + str(server.port) + + "?ign_set_control", + ) self._check_output(output) def test_highspeed_flash_virtual_port(self): with ESPRFC2217Server() as server: - rfc2217_port = 'rfc2217://localhost:' + str(server.port) + '?ign_set_control' - self.run_esptool("write_flash 0x0 images/fifty_kb.bin", baud=921600, rfc2217_port=rfc2217_port) + rfc2217_port = ( + "rfc2217://localhost:" + str(server.port) + "?ign_set_control" + ) + self.run_esptool( + "write_flash 0x0 images/fifty_kb.bin", + baud=921600, + rfc2217_port=rfc2217_port, + ) self.verify_readback(0, 50 * 1024, "images/fifty_kb.bin") @@ -780,7 +927,11 @@ class TestReadWriteMemory(EsptoolTestCase): def _test_read_write(self, esp): # find the start of one of these named memory regions test_addr = None - for test_region in ["RTC_DRAM", "RTC_DATA", "DRAM"]: # find a probably-unused memory type + for test_region in [ + "RTC_DRAM", + "RTC_DATA", + "DRAM", + ]: # find a probably-unused memory type region = esp.get_memory_region(test_region) if region: test_addr = region[0] @@ -800,18 +951,25 @@ class TestReadWriteMemory(EsptoolTestCase): self.assertEqual(esp.read_reg(test_addr), 0x555) def test_read_write_memory_rom(self): - esp = esptool.get_default_connected_device([serialport], serialport, 10, 115200, chip) + esp = esptool.get_default_connected_device( + [serialport], serialport, 10, 115200, chip + ) self._test_read_write(esp) def test_read_write_memory_stub(self): - esp = esptool.get_default_connected_device([serialport], serialport, 10, 115200, chip) + esp = esptool.get_default_connected_device( + [serialport], serialport, 10, 115200, chip + ) esp = esp.run_stub() self._test_read_write(esp) -if __name__ == '__main__': +if __name__ == "__main__": if len(sys.argv) < 3: - print("Usage: %s [--trace] [optional default baud rate] [optional tests]" % sys.argv[0]) + print( + "Usage: %s [--trace] " + "[optional default baud rate] [optional tests]" % sys.argv[0] + ) sys.exit(1) serialport = sys.argv[1] # chip is already set to sys.argv[2], so @skipUnless can evaluate against it @@ -825,25 +983,29 @@ if __name__ == '__main__': pass # arg3 not a number, must be a test name # unittest also uses argv, so trim the args we used - sys.argv = [sys.argv[0]] + sys.argv[args_used + 1:] + sys.argv = [sys.argv[0]] + sys.argv[args_used + 1 :] # esptool skips strapping mode check in USB CDC case, if this is set os.environ["ESPTOOL_TESTING"] = "1" print("Running esptool.py tests...") try: - import xmlrunner # it should come from the unittest-xml-reporting package and not from xmlrunner + # it should come from the unittest-xml-reporting package and not from xmlrunner + import xmlrunner import pkg_resources try: - pkg_resources.require('xmlrunner') - raise ImportError('The unittest-xml-reporting package should be used instead of xmlrunner') + pkg_resources.require("xmlrunner") + raise ImportError( + "The unittest-xml-reporting package should be used instead of xmlrunner" + ) except pkg_resources.DistributionNotFound: - # it is desired that xmlrunner is not installed so it will not interfere with unittest-xml-reporting + # it is desired that xmlrunner is not installed + # so it will not interfere with unittest-xml-reporting # (conflict of files) pass - with io.open('report.xml', 'wb') as output: + with io.open("report.xml", "wb") as output: unittest.main(testRunner=xmlrunner.XMLTestRunner(output=output)) except ImportError: unittest.main(buffer=True) diff --git a/test/test_imagegen.py b/test/test_imagegen.py index dd71e98..23536fe 100755 --- a/test/test_imagegen.py +++ b/test/test_imagegen.py @@ -31,7 +31,7 @@ def try_delete(path): def segment_matches_section(segment, section): - """ segment is an ImageSegment from an esptool binary. + """segment is an ImageSegment from an esptool binary. section is an elftools ELF section Returns True if they match @@ -41,7 +41,6 @@ def segment_matches_section(segment, section): class BaseTestCase(unittest.TestCase): - def assertEqualHex(self, expected, actual, message=None): try: expected = hex(expected) @@ -66,8 +65,14 @@ class BaseTestCase(unittest.TestCase): data = section.data() # no section should start at the same address as the ELF section. for seg in sorted(image.segments, key=lambda s: s.addr): - print("comparing seg 0x%x sec 0x%x len 0x%x" % (seg.addr, sh_addr, len(data))) - self.assertFalse(seg.addr == sh_addr, "%s should not be in the binary image" % section_name) + print( + "comparing seg 0x%x sec 0x%x len 0x%x" + % (seg.addr, sh_addr, len(data)) + ) + self.assertFalse( + seg.addr == sh_addr, + "%s should not be in the binary image" % section_name, + ) def assertImageContainsSection(self, image, elf, section_name): """ @@ -85,18 +90,28 @@ class BaseTestCase(unittest.TestCase): # as we find it in the image segments. When we're done 'data' should # all be accounted for for seg in sorted(image.segments, key=lambda s: s.addr): - print("comparing seg 0x%x sec 0x%x len 0x%x" % (seg.addr, sh_addr, len(data))) + print( + "comparing seg 0x%x sec 0x%x len 0x%x" + % (seg.addr, sh_addr, len(data)) + ) if seg.addr == sh_addr: overlap_len = min(len(seg.data), len(data)) - self.assertEqual(data[:overlap_len], seg.data[:overlap_len], - "ELF '%s' section has mis-matching binary image data" % section_name) + self.assertEqual( + data[:overlap_len], + seg.data[:overlap_len], + "ELF '%s' section has mis-matching binary image data" + % section_name, + ) sh_addr += overlap_len data = data[overlap_len:] # no bytes in 'data' should be left unmatched - self.assertEqual(0, len(data), - "ELF %s section '%s' has no encompassing segment(s) in binary image (image segments: %s)" - % (elf, section_name, image.segments)) + self.assertEqual( + 0, + len(data), + "ELF %s section '%s' has no encompassing segment(s) in binary image " + "(image segments: %s)" % (elf, section_name, image.segments), + ) def assertImageInfo(self, binpath, chip="esp8266"): """ @@ -111,10 +126,12 @@ class BaseTestCase(unittest.TestCase): print(e.output) raise self.assertFalse("invalid" in output, "Checksum calculation should be valid") - self.assertFalse("warning" in output.lower(), "Should be no warnings in image_info output") + self.assertFalse( + "warning" in output.lower(), "Should be no warnings in image_info output" + ) def run_elf2image(self, chip, elf_path, version=None, extra_args=[]): - """ Run elf2image on elf_path """ + """Run elf2image on elf_path""" cmd = [sys.executable, ESPTOOL_PY, "--chip", chip, "elf2image"] if version is not None: cmd += ["--version", str(version)] @@ -123,7 +140,9 @@ class BaseTestCase(unittest.TestCase): try: output = str(subprocess.check_output(cmd)) print(output) - self.assertFalse("warning" in output.lower(), "elf2image should not output warnings") + self.assertFalse( + "warning" in output.lower(), "elf2image should not output warnings" + ) except subprocess.CalledProcessError as e: print(e.output) raise @@ -145,9 +164,11 @@ class ESP8266V1ImageTests(BaseTestCase): with open(self.ELF, "rb") as f: e = ELFFile(f) irom_section = e.get_section_by_name(".irom0.text") - self.assertEqual(irom_section.header.sh_size, - os.stat(self.BIN_IROM).st_size, - "IROM raw binary file should be same length as .irom0.text section") + self.assertEqual( + irom_section.header.sh_size, + os.stat(self.BIN_IROM).st_size, + "IROM raw binary file should be same length as .irom0.text section", + ) def test_loaded_sections(self): image = esptool.bin_image.LoadFirmwareImage("esp8266", self.BIN_LOAD) @@ -162,8 +183,9 @@ class ESP8266V1ImageTests(BaseTestCase): class ESP8266V12SectionHeaderNotAtEnd(BaseTestCase): - """ Ref https://github.com/espressif/esptool/issues/197 - - this ELF image has the section header not at the end of the file """ + """Ref https://github.com/espressif/esptool/issues/197 - + this ELF image has the section header not at the end of the file""" + ELF = "esp8266-nonossdkv12-example.elf" BIN_LOAD = ELF + "-0x00000.bin" BIN_IROM = ELF + "-0x40000.bin" @@ -182,7 +204,6 @@ class ESP8266V12SectionHeaderNotAtEnd(BaseTestCase): class ESP8266V2ImageTests(BaseTestCase): - def _test_elf2image(self, elfpath, binpath, mergedsections=[]): try: self.run_elf2image("esp8266", elfpath, 2) @@ -198,13 +219,20 @@ class ESP8266V2ImageTests(BaseTestCase): self.assertImageDoesNotContainSection(image, elfpath, sec) irom_segment = image.segments[0] - self.assertEqual(0, irom_segment.addr, - "IROM segment 'load address' should be zero") + self.assertEqual( + 0, irom_segment.addr, "IROM segment 'load address' should be zero" + ) with open(elfpath, "rb") as f: e = ELFFile(f) - sh_size = (e.get_section_by_name(".irom0.text").header.sh_size + 15) & ~15 - self.assertEqual(len(irom_segment.data), sh_size, "irom segment (0x%x) should be same size (16 padded) as .irom0.text section (0x%x)" - % (len(irom_segment.data), sh_size)) + sh_size = ( + e.get_section_by_name(".irom0.text").header.sh_size + 15 + ) & ~15 + self.assertEqual( + len(irom_segment.data), + sh_size, + "irom segment (0x%x) should be same size (16 padded) " + "as .irom0.text section (0x%x)" % (len(irom_segment.data), sh_size), + ) # check V2 CRC (for ESP8266 SDK bootloader) with open(binpath, "rb") as f: @@ -248,8 +276,7 @@ class ESP32ImageTests(BaseTestCase): BIN = "esp32-bootloader.bin" image = self._test_elf2image(ELF, BIN) self.assertEqual(3, len(image.segments)) - for section in [".iram1.text", ".iram_pool_1.text", - ".dram0.rodata"]: + for section in [".iram1.text", ".iram_pool_1.text", ".dram0.rodata"]: self.assertImageContainsSection(image, ELF, section) def test_app_template(self): @@ -260,8 +287,12 @@ class ESP32ImageTests(BaseTestCase): # equal 5 (instead of 6). self.assertEqual(5, len(image.segments)) # the other segment is a padding or merged segment - for section in [".iram0.vectors", ".dram0.data", - ".flash.rodata", ".flash.text"]: + for section in [ + ".iram0.vectors", + ".dram0.data", + ".flash.rodata", + ".flash.text", + ]: self.assertImageContainsSection(image, ELF, section) # check that merged sections are not in the binary image for mergedsection in [".iram0.text"]: @@ -279,7 +310,8 @@ class ESP32ImageTests(BaseTestCase): def test_use_segments(self): ELF = "esp32-zephyr.elf" BIN = "esp32-zephyr.bin" - # default behaviour uses ELF sections, this ELF will produce 8 segments in the bin + # default behaviour uses ELF sections, + # this ELF will produce 8 segments in the bin image = self._test_elf2image(ELF, BIN) # Adjacent sections are now merged, len(image.segments) should # equal 4 (instead of 8). @@ -295,11 +327,16 @@ class ESP8266FlashHeaderTests(BaseTestCase): ELF = "esp8266-nonossdkv20-at-v2.elf" BIN = "esp8266-nonossdkv20-at-v2-0x01000.bin" try: - self.run_elf2image("esp8266", ELF, version=2, extra_args=["--flash_size", "2MB", "--flash_mode", "dio"]) + self.run_elf2image( + "esp8266", + ELF, + version=2, + extra_args=["--flash_size", "2MB", "--flash_mode", "dio"], + ) with open(BIN, "rb") as f: header = f.read(4) print("header %r" % header) - self.assertEqualHex(0xea, header[0]) + self.assertEqualHex(0xEA, header[0]) self.assertEqualHex(0x02, header[2]) self.assertEqualHex(0x30, header[3]) finally: @@ -311,10 +348,21 @@ class ESP32FlashHeaderTests(BaseTestCase): ELF = "esp32-app-template.elf" BIN = "esp32-app-template.bin" try: - self.run_elf2image("esp32", ELF, extra_args=["--flash_size", "16MB", "--flash_mode", "dio", "--min-rev", "1"]) + self.run_elf2image( + "esp32", + ELF, + extra_args=[ + "--flash_size", + "16MB", + "--flash_mode", + "dio", + "--min-rev", + "1", + ], + ) with open(BIN, "rb") as f: header = f.read(24) - self.assertEqualHex(0xe9, header[0]) + self.assertEqualHex(0xE9, header[0]) self.assertEqualHex(0x02, header[2]) self.assertEqualHex(0x40, header[3]) self.assertEqualHex(0x01, header[14]) # chip revision @@ -324,7 +372,7 @@ class ESP32FlashHeaderTests(BaseTestCase): class ELFSHA256Tests(BaseTestCase): ELF = "esp32-app-cust-ver-info.elf" - SHA_OFFS = 0xb0 # absolute offset of the SHA in the .bin file + SHA_OFFS = 0xB0 # absolute offset of the SHA in the .bin file BIN = "esp32-app-cust-ver-info.bin" """ @@ -339,14 +387,17 @@ class ELFSHA256Tests(BaseTestCase): .time = "xxxxxxxxxxxxxxx", .date = "yyyyyyyyyyyyyyy", .idf_ver = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz", - .app_elf_sha256 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - .reserv2 = {0xffffffff,0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, - 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, + .app_elf_sha256 = + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + .reserv2 = {0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, + 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}, }; - This leaves zeroes only for the fiels of SHA-256 and the test will fail if the placement of zeroes are tested at - the wrong place. + This leaves zeroes only for the fiels of SHA-256 and the test will fail + if the placement of zeroes are tested at the wrong place. 00000000: e907 0020 780f 0840 ee00 0000 0000 0000 ... x..@........ 00000010: 0000 0000 0000 0001 2000 403f 605a 0000 ........ .@?`Z.. @@ -359,7 +410,7 @@ class ELFSHA256Tests(BaseTestCase): 00000080: 7979 7979 7979 7979 7979 7979 7979 7900 yyyyyyyyyyyyyyy. 00000090: 7a7a 7a7a 7a7a 7a7a 7a7a 7a7a 7a7a 7a7a zzzzzzzzzzzzzzzz 000000a0: 7a7a 7a7a 7a7a 7a7a 7a7a 7a7a 7a7a 7a00 zzzzzzzzzzzzzzz. - 000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ SHA-256 should go here + 000000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ SHA-256 here 000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000d0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 000000e0: ffff ffff ffff ffff ffff ffff ffff ffff ................ @@ -372,10 +423,16 @@ class ELFSHA256Tests(BaseTestCase): def test_binary_patched(self): try: - self.run_elf2image("esp32", self.ELF, extra_args=["--elf-sha256-offset", "0x%x" % self.SHA_OFFS]) + self.run_elf2image( + "esp32", + self.ELF, + extra_args=["--elf-sha256-offset", "0x%x" % self.SHA_OFFS], + ) image = esptool.bin_image.LoadFirmwareImage("esp32", self.BIN) rodata_segment = image.segments[0] - bin_sha256 = rodata_segment.data[self.SHA_OFFS - 0x20: self.SHA_OFFS - 0x20 + 32] # subtract 0x20 byte header here + bin_sha256 = rodata_segment.data[ + self.SHA_OFFS - 0x20 : self.SHA_OFFS - 0x20 + 32 + ] # subtract 0x20 byte header here with open(self.ELF, "rb") as f: elf_computed_sha256 = hashlib.sha256(f.read()).digest() @@ -391,12 +448,16 @@ class ELFSHA256Tests(BaseTestCase): def test_no_overwrite_data(self): with self.assertRaises(subprocess.CalledProcessError) as e: - self.run_elf2image("esp32", "esp32-bootloader.elf", extra_args=["--elf-sha256-offset", "0xb0"]) + self.run_elf2image( + "esp32", + "esp32-bootloader.elf", + extra_args=["--elf-sha256-offset", "0xb0"], + ) output = e.exception.output self.assertIn(b"SHA256", output) self.assertIn(b"zero", output) -if __name__ == '__main__': +if __name__ == "__main__": print("Running image generation tests...") unittest.main(buffer=True) diff --git a/test/test_merge_bin.py b/test/test_merge_bin.py index c14b390..3041f31 100755 --- a/test/test_merge_bin.py +++ b/test/test_merge_bin.py @@ -28,9 +28,8 @@ def read_image(filename): class MergeBinTests(unittest.TestCase): - def run_merge_bin(self, chip, offsets_names, options=[]): - """ Run merge_bin on a list of (offset, filename) tuples + """Run merge_bin on a list of (offset, filename) tuples with output to a named temporary file. Filenames are relative to the 'test/images' directory. @@ -41,14 +40,26 @@ class MergeBinTests(unittest.TestCase): try: output_file.close() - cmd = [sys.executable, ESPTOOL_PY, "--chip", chip, "merge_bin", "-o", output_file.name] + options + cmd = [ + sys.executable, + ESPTOOL_PY, + "--chip", + chip, + "merge_bin", + "-o", + output_file.name, + ] + options for (offset, name) in offsets_names: cmd += [hex(offset), name] print("Executing %s" % (" ".join(cmd))) - output = str(subprocess.check_output(cmd, cwd=IMAGES_DIR, stderr=subprocess.STDOUT)) + output = str( + subprocess.check_output(cmd, cwd=IMAGES_DIR, stderr=subprocess.STDOUT) + ) print(output) - self.assertFalse("warning" in output.lower(), "merge_bin should not output warnings") + self.assertFalse( + "warning" in output.lower(), "merge_bin should not output warnings" + ) with open(output_file.name, "rb") as f: return f.read() @@ -59,13 +70,15 @@ class MergeBinTests(unittest.TestCase): os.unlink(output_file.name) def assertAllFF(self, some_bytes): - # this may need some improving as the failed assert messages may be very long and/or useless! - self.assertEqual(b'\xFF' * len(some_bytes), some_bytes) + # this may need some improving as the failed assert messages may be + # very long and/or useless! + self.assertEqual(b"\xFF" * len(some_bytes), some_bytes) def test_simple_merge(self): - merged = self.run_merge_bin("esp8266", [(0x0, "one_kb.bin"), - (0x1000, "one_kb.bin"), - (0x10000, "one_kb.bin")]) + merged = self.run_merge_bin( + "esp8266", + [(0x0, "one_kb.bin"), (0x1000, "one_kb.bin"), (0x10000, "one_kb.bin")], + ) one_kb = read_image("one_kb.bin") self.assertEqual(0x400, len(one_kb)) @@ -80,16 +93,16 @@ class MergeBinTests(unittest.TestCase): def test_args_out_of_order(self): # no matter which order we supply arguments, the output should be the same - args = [(0x0, "one_kb.bin"), - (0x1000, "one_kb.bin"), - (0x10000, "one_kb.bin")] - merged_orders = [self.run_merge_bin("esp8266", perm_args) for perm_args in itertools.permutations(args)] + args = [(0x0, "one_kb.bin"), (0x1000, "one_kb.bin"), (0x10000, "one_kb.bin")] + merged_orders = [ + self.run_merge_bin("esp8266", perm_args) + for perm_args in itertools.permutations(args) + ] for m in merged_orders: self.assertEqual(merged_orders[0], m) def test_error_overlap(self): - args = [(0x1000, "one_mb.bin"), - (0x20000, "one_kb.bin")] + args = [(0x1000, "one_mb.bin"), (0x20000, "one_kb.bin")] for perm_args in itertools.permutations(args): with self.assertRaises(subprocess.CalledProcessError) as fail: self.run_merge_bin("esp32", perm_args) @@ -101,16 +114,23 @@ class MergeBinTests(unittest.TestCase): self.assertEqual(read_image("one_mb.bin"), merged[0x100000:]) def test_update_bootloader_params(self): - merged = self.run_merge_bin("esp32", [(0x1000, "bootloader_esp32.bin"), (0x10000, "ram_helloworld/helloworld-esp32.bin")], - ["--flash_size", "2MB", "--flash_mode", "dout"]) + merged = self.run_merge_bin( + "esp32", + [ + (0x1000, "bootloader_esp32.bin"), + (0x10000, "ram_helloworld/helloworld-esp32.bin"), + ], + ["--flash_size", "2MB", "--flash_mode", "dout"], + ) self.assertAllFF(merged[:0x1000]) bootloader = read_image("bootloader_esp32.bin") helloworld = read_image("ram_helloworld/helloworld-esp32.bin") - # test the bootloader is unchanged apart from the header (updating the header doesn't change CRC, + # test the bootloader is unchanged apart from the header + # (updating the header doesn't change CRC, # and doesn't update the SHA although it will invalidate it!) - self.assertEqual(merged[0x1010:0x1000 + len(bootloader)], bootloader[0x10:]) + self.assertEqual(merged[0x1010 : 0x1000 + len(bootloader)], bootloader[0x10:]) # check the individual bytes in the header are as expected merged_hdr = merged[0x1000:0x1010] @@ -118,44 +138,63 @@ class MergeBinTests(unittest.TestCase): self.assertEqual(bootloader_hdr[:2], merged_hdr[:2]) self.assertEqual(3, byte(merged_hdr, 2)) # flash mode dout self.assertEqual(0x10, byte(merged_hdr, 3) & 0xF0) # flash size 2MB (ESP32) - self.assertEqual(byte(bootloader_hdr, 3) & 0x0F, byte(merged_hdr, 3) & 0x0F) # flash speed is unchanged - self.assertEqual(bootloader_hdr[4:], merged_hdr[4:]) # remaining field are unchanged + self.assertEqual( + byte(bootloader_hdr, 3) & 0x0F, byte(merged_hdr, 3) & 0x0F + ) # flash speed is unchanged + self.assertEqual( + bootloader_hdr[4:], merged_hdr[4:] + ) # remaining field are unchanged # check all the padding is as expected - self.assertAllFF(merged[0x1000 + len(bootloader):0x10000]) - self.assertEqual(merged[0x10000:0x10000 + len(helloworld)], helloworld) + self.assertAllFF(merged[0x1000 + len(bootloader) : 0x10000]) + self.assertEqual(merged[0x10000 : 0x10000 + len(helloworld)], helloworld) def test_target_offset(self): - merged = self.run_merge_bin("esp32", [(0x1000, "bootloader_esp32.bin"), (0x10000, "ram_helloworld/helloworld-esp32.bin")], - ["--target-offset", "0x1000"]) + merged = self.run_merge_bin( + "esp32", + [ + (0x1000, "bootloader_esp32.bin"), + (0x10000, "ram_helloworld/helloworld-esp32.bin"), + ], + ["--target-offset", "0x1000"], + ) bootloader = read_image("bootloader_esp32.bin") helloworld = read_image("ram_helloworld/helloworld-esp32.bin") - self.assertEqual(bootloader, merged[:len(bootloader)]) - self.assertEqual(helloworld, merged[0xF000:0xF000 + len(helloworld)]) - self.assertAllFF(merged[0x1000 + len(bootloader):0xF000]) + self.assertEqual(bootloader, merged[: len(bootloader)]) + self.assertEqual(helloworld, merged[0xF000 : 0xF000 + len(helloworld)]) + self.assertAllFF(merged[0x1000 + len(bootloader) : 0xF000]) def test_fill_flash_size(self): - merged = self.run_merge_bin("esp32c3", [(0x0, "bootloader_esp32c3.bin")], - ["--fill-flash-size", "4MB"]) + merged = self.run_merge_bin( + "esp32c3", [(0x0, "bootloader_esp32c3.bin")], ["--fill-flash-size", "4MB"] + ) bootloader = read_image("bootloader_esp32c3.bin") self.assertEqual(0x400000, len(merged)) - self.assertEqual(bootloader, merged[:len(bootloader)]) - self.assertAllFF(merged[len(bootloader):]) + self.assertEqual(bootloader, merged[: len(bootloader)]) + self.assertAllFF(merged[len(bootloader) :]) def test_fill_flash_size_w_target_offset(self): - merged = self.run_merge_bin("esp32", [(0x1000, "bootloader_esp32.bin"), (0x10000, "ram_helloworld/helloworld-esp32.bin")], - ["--target-offset", "0x1000", "--fill-flash-size", "2MB"]) + merged = self.run_merge_bin( + "esp32", + [ + (0x1000, "bootloader_esp32.bin"), + (0x10000, "ram_helloworld/helloworld-esp32.bin"), + ], + ["--target-offset", "0x1000", "--fill-flash-size", "2MB"], + ) - self.assertEqual(0x200000 - 0x1000, len(merged)) # full length is without target-offset arg + self.assertEqual( + 0x200000 - 0x1000, len(merged) + ) # full length is without target-offset arg bootloader = read_image("bootloader_esp32.bin") helloworld = read_image("ram_helloworld/helloworld-esp32.bin") - self.assertEqual(bootloader, merged[:len(bootloader)]) - self.assertEqual(helloworld, merged[0xF000:0xF000 + len(helloworld)]) - self.assertAllFF(merged[0xF000 + len(helloworld):]) + self.assertEqual(bootloader, merged[: len(bootloader)]) + self.assertEqual(helloworld, merged[0xF000 : 0xF000 + len(helloworld)]) + self.assertAllFF(merged[0xF000 + len(helloworld) :]) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main(buffer=True) diff --git a/test/test_modules.py b/test/test_modules.py index 43015e9..f84c39b 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -# Tests for regressions in python modules used by esptool.py, espefuse.py, and espsecure.py +# Tests for regressions in python modules +# used by esptool.py, espefuse.py, and espsecure.py from __future__ import division, print_function import unittest @@ -10,6 +11,7 @@ import reedsolo class ReedSoloTests(unittest.TestCase): def test_reed_solomon_encoding(self): + # fmt: off pairs = [("a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf", "0404992ae0b12cb0ef0d4fd3"), ("11a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbd11bf", "e001803c2130884c190d57d5"), ("22a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbd22bf", "6c32056dd3fcc33fa6193773"), @@ -30,15 +32,17 @@ class ReedSoloTests(unittest.TestCase): ("0001000100010001000a000b000c000d000e000f000100010001000100010001", "6dc2afb4820bb002d9263544"), ("0000000000000000000000000000000000000000000000000000000000000001", "44774376dc1f07545c7fd561"), ] # Pregenerated pairs consisting of 32 bytes of data + 12 bytes of RS ECC (FPGA verified) + # fmt: on rs = reedsolo.RSCodec(12) # 12 ECC symbols for pair in pairs: bin_base = bytearray.fromhex(pair[0]) - encoded_data = rs.encode([x for x in bin_base]) # Encode the original 32 bytes of data + # Encode the original 32 bytes of data + encoded_data = rs.encode([x for x in bin_base]) self.assertEqual(encoded_data, bytearray.fromhex(pair[0] + pair[1])) -if __name__ == '__main__': +if __name__ == "__main__": print("Running python modules tests...") unittest.main(buffer=True)