diff --git a/docs/conf_common.py b/docs/conf_common.py
index 36ecf4ff7..e7ad6e2dd 100644
--- a/docs/conf_common.py
+++ b/docs/conf_common.py
@@ -159,6 +159,7 @@ def update_exclude_patterns(tags):
# note: in toctrees, these also need to be marked with a :esp32: filter
for e in ['esp32s2.rst',
'hw-reference/esp32s2/**',
+ 'api-guides/dfu.rst',
'api-guides/ulps2_instruction_set.rst',
'api-reference/peripherals/hmac.rst',
'api-reference/peripherals/temp_sensor.rst']:
diff --git a/docs/en/api-guides/dfu.rst b/docs/en/api-guides/dfu.rst
new file mode 100644
index 000000000..251b58c5c
--- /dev/null
+++ b/docs/en/api-guides/dfu.rst
@@ -0,0 +1,99 @@
+***********************************************
+Device Firmware Upgrade through USB
+***********************************************
+
+.. only:: esp32
+
+ .. note::
+ Device Firmware Upgrade through USB is not supported with ESP32 chips.
+
+Device Firmware Upgrade (DFU) is a mechanism for upgrading the firmware of devices through Universal Serial Bus (USB).
+DFU is supported by ESP32-S2 chips. The necessary connections for the USB peripheral are shown in the following table.
+
++------+-------------+
+| GPIO | USB |
++======+=============+
+| 19 | D- (green) |
++------+-------------+
+| 20 | D+ (white) |
++------+-------------+
+| GND | GND (black) |
++------+-------------+
+| | +5V (red) |
++------+-------------+
+
+The software requirements of DFU are included in :ref:`get-started-get-prerequisites` of the Getting Started Guide.
+
+Section :ref:`api_guide_dfu_build` describes how to build firmware for DFU with ESP-IDF and
+Section :ref:`api_guide_dfu_flash` deals with flashing the firmware.
+
+.. _api_guide_dfu_build:
+
+Building the DFU Image
+======================
+
+The DFU image can be created by running::
+
+ idf.py dfu
+
+which creates ``dfu.bin`` in the build directory.
+
+.. note::
+ Don't forget to set the target chip by ``idf.py set-target`` before running ``idf.py dfu``. Otherwise, you might
+ create an image for a different chip or receive an error message something like ``unknown target 'dfu'``.
+
+.. _api_guide_dfu_flash:
+
+Flashing the Chip with the DFU Image
+====================================
+
+The DFU image is downloaded into the chip by running::
+
+ idf.py dfu-flash
+
+which relies on `dfu-util `_. Please see :ref:`get-started-get-prerequisites` for
+installing ``dfu-util``. ``dfu-util`` needs additional setup for :ref:`api_guide_dfu_flash_win` or setting up an
+:ref:`api_guide_dfu_flash_udev`. Mac OS users should be able to use ``dfu-util`` without further setup.
+
+See :ref:`api_guide_dfu_flash_errors` and their solutions.
+
+.. _api_guide_dfu_flash_udev:
+
+udev rule (Linux only)
+----------------------
+
+udev is a device manager for the Linux kernel. It allows us to run ``dfu-util`` (and ``idf.py dfu-flash``) without
+``sudo`` for gaining access to the chip.
+
+Create file ``/etc/udev/rules.d/40-dfuse.rules`` with the following content::
+
+ SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="00??", GROUP="plugdev", MODE="0666"
+
+.. note::
+ Please check the output of command ``groups``. The user has to be a member of the `GROUP` specified above. You may
+ use some other existing group for this purpose (e.g. `uucp` on some systems instead of `plugdev`) or create a new
+ group for this purpose.
+
+Restart your computer so the previous setting could take into affect or run ``sudo udevadm trigger`` to force
+manually udev to trigger your new rule.
+
+.. _api_guide_dfu_flash_win:
+
+USB drivers (Windows only)
+--------------------------
+
+``dfu-util`` uses `libusb` to access the device. You have to register on Windows the device with the `WinUSB` driver.
+Please see the `libusb wiki `_ for more
+details.
+
+.. _api_guide_dfu_flash_errors:
+
+Common errors
+-------------
+
+- ``dfu-util: command not found`` might indicate that the tool hasn't been installed or is not available from the terminal.
+ An easy way of checking the tool is running ``dfu-util --version``. Please see :ref:`get-started-get-prerequisites` for
+ installing ``dfu-util``.
+- The reason for ``No DFU capable USB device available`` could be that the USB driver wasn't properly installed on
+ Windows (see :ref:`api_guide_dfu_flash_win`) or udev rule was not setup on Linux
+ (see :ref:`api_guide_dfu_flash_udev`).
diff --git a/docs/en/api-guides/index.rst b/docs/en/api-guides/index.rst
index 4b11122ac..946b85f6c 100644
--- a/docs/en/api-guides/index.rst
+++ b/docs/en/api-guides/index.rst
@@ -11,6 +11,7 @@ API Guides
Build System
:esp32: Build System (Legacy GNU Make)
Deep Sleep Wake Stubs
+ :esp32s2: Device Firmware Upgrade through USB
Error Handling
:esp32: ESP-BLE-MESH
ESP-MESH (Wi-Fi)
diff --git a/docs/en/api-guides/tools/idf-tools-notes.inc b/docs/en/api-guides/tools/idf-tools-notes.inc
index ffd58cd36..426b33edb 100644
--- a/docs/en/api-guides/tools/idf-tools-notes.inc
+++ b/docs/en/api-guides/tools/idf-tools-notes.inc
@@ -47,4 +47,9 @@ On Linux and macOS, it is recommended to install ninja using the OS-specific pac
.. tool-ccache-notes
+---
+
+.. tool-dfu-util-notes
+
+
---
diff --git a/docs/en/get-started/linux-setup-scratch.rst b/docs/en/get-started/linux-setup-scratch.rst
index 1ba2f560d..07b236c9a 100644
--- a/docs/en/get-started/linux-setup-scratch.rst
+++ b/docs/en/get-started/linux-setup-scratch.rst
@@ -15,15 +15,15 @@ To compile with ESP-IDF you need to get the following packages:
- CentOS 7::
- sudo yum install git wget ncurses-devel flex bison gperf python pyserial python-pyelftools cmake ninja-build ccache
+ sudo yum install git wget ncurses-devel flex bison gperf python pyserial python-pyelftools cmake ninja-build ccache dfu-util
- Ubuntu and Debian::
- sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache libffi-dev libssl-dev
+ sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache libffi-dev libssl-dev dfu-util
- Arch::
- sudo pacman -S --needed gcc git make ncurses flex bison gperf python-pyserial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja ccache
+ sudo pacman -S --needed gcc git make ncurses flex bison gperf python-pyserial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja ccache dfu-util
.. note::
CMake version 3.5 or newer is required for use with ESP-IDF. Older Linux distributions may require updating, enabling of a "backports" repository, or installing of a "cmake3" package rather than "cmake".
diff --git a/docs/en/get-started/linux-setup.rst b/docs/en/get-started/linux-setup.rst
index bde40fe2a..80fcc75b1 100644
--- a/docs/en/get-started/linux-setup.rst
+++ b/docs/en/get-started/linux-setup.rst
@@ -11,15 +11,15 @@ To compile with ESP-IDF you need to get the following packages:
- CentOS 7::
- sudo yum install git wget flex bison gperf python cmake ninja-build ccache
+ sudo yum install git wget flex bison gperf python cmake ninja-build ccache dfu-util
- Ubuntu and Debian::
- sudo apt-get install git wget flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev
+ sudo apt-get install git wget flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util
- Arch::
- sudo pacman -S --needed gcc git make flex bison gperf python-pip cmake ninja ccache
+ sudo pacman -S --needed gcc git make flex bison gperf python-pip cmake ninja ccache dfu-util
.. note::
CMake version 3.5 or newer is required for use with ESP-IDF. Older Linux distributions may require updating, enabling of a "backports" repository, or installing of a "cmake3" package rather than "cmake".
diff --git a/docs/en/get-started/macos-setup-scratch.rst b/docs/en/get-started/macos-setup-scratch.rst
index e906b9fe7..122373038 100644
--- a/docs/en/get-started/macos-setup-scratch.rst
+++ b/docs/en/get-started/macos-setup-scratch.rst
@@ -31,11 +31,11 @@ Install Prerequisites
- If you have HomeBrew, you can run::
- brew install cmake ninja
+ brew install cmake ninja dfu-util
- If you have MacPorts, you can run::
- sudo port install cmake ninja
+ sudo port install cmake ninja dfu-util
Compile the Toolchain from Source
=================================
diff --git a/docs/en/get-started/macos-setup.rst b/docs/en/get-started/macos-setup.rst
index caede42ec..5a072c3d7 100644
--- a/docs/en/get-started/macos-setup.rst
+++ b/docs/en/get-started/macos-setup.rst
@@ -21,11 +21,11 @@ ESP-IDF will use the version of Python installed by default on macOS.
- If you have HomeBrew_, you can run::
- brew install cmake ninja
+ brew install cmake ninja dfu-util
- If you have MacPorts_, you can run::
- sudo port install cmake ninja
+ sudo port install cmake ninja dfu-util
- Otherwise, consult the CMake_ and Ninja_ home pages for macOS installation downloads.
diff --git a/docs/zh_CN/api-guides/dfu.rst b/docs/zh_CN/api-guides/dfu.rst
new file mode 100644
index 000000000..566cdee39
--- /dev/null
+++ b/docs/zh_CN/api-guides/dfu.rst
@@ -0,0 +1 @@
+.. include:: ../../en/api-guides/dfu.rst
diff --git a/docs/zh_CN/api-guides/index.rst b/docs/zh_CN/api-guides/index.rst
index 09a32d945..9a08e2ad3 100644
--- a/docs/zh_CN/api-guides/index.rst
+++ b/docs/zh_CN/api-guides/index.rst
@@ -12,6 +12,7 @@ API 指南
严重错误
Event Handling
Deep Sleep Wake Stubs
+ :esp32s2: Device Firmware Upgrade through USB
ESP32 Core Dump
Flash Encryption <../security/flash-encryption>
FreeRTOS SMP Changes
diff --git a/docs/zh_CN/api-guides/tools/idf-tools-notes.inc b/docs/zh_CN/api-guides/tools/idf-tools-notes.inc
index c7c750089..946e8222c 100644
--- a/docs/zh_CN/api-guides/tools/idf-tools-notes.inc
+++ b/docs/zh_CN/api-guides/tools/idf-tools-notes.inc
@@ -49,4 +49,9 @@ On Linux and macOS, it is recommended to install ninja using the OS package mana
.. tool-ccache-notes
+---
+
+.. tool-dfu-util-notes
+
+
---
diff --git a/docs/zh_CN/get-started/linux-setup-scratch.rst b/docs/zh_CN/get-started/linux-setup-scratch.rst
index 3d8bee637..5964e4c3c 100644
--- a/docs/zh_CN/get-started/linux-setup-scratch.rst
+++ b/docs/zh_CN/get-started/linux-setup-scratch.rst
@@ -13,15 +13,15 @@
- CentOS 7::
- sudo yum install git wget ncurses-devel flex bison gperf python pyserial python-pyelftools cmake ninja-build ccache
+ sudo yum install git wget ncurses-devel flex bison gperf python pyserial python-pyelftools cmake ninja-build ccache dfu-util
- Ubuntu 和 Debian::
- sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache libffi-dev libssl-dev
+ sudo apt-get install git wget libncurses-dev flex bison gperf python python-pip python-setuptools python-serial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja-build ccache libffi-dev libssl-dev dfu-util
- Arch::
- sudo pacman -S --needed gcc git make ncurses flex bison gperf python-pyserial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja ccache
+ sudo pacman -S --needed gcc git make ncurses flex bison gperf python-pyserial python-click python-cryptography python-future python-pyparsing python-pyelftools cmake ninja ccache dfu-util
.. note::
使用 ESP-IDF 需要 CMake 3.5 或以上版本。较早版本的 Linux 可能需要升级才能向后移植仓库,或安装 "cmake3" 软件包,而不是安装 "cmake"。
diff --git a/docs/zh_CN/get-started/linux-setup.rst b/docs/zh_CN/get-started/linux-setup.rst
index 4f6019857..38b04a855 100644
--- a/docs/zh_CN/get-started/linux-setup.rst
+++ b/docs/zh_CN/get-started/linux-setup.rst
@@ -11,15 +11,15 @@ Linux 平台工具链的标准设置
- CentOS 7::
- sudo yum install git wget flex bison gperf python cmake ninja-build ccache
+ sudo yum install git wget flex bison gperf python cmake ninja-build ccache dfu-util
- Ubuntu 和 Debian::
- sudo apt-get install git wget flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev
+ sudo apt-get install git wget flex bison gperf python python-pip python-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util
- Arch::
- sudo pacman -S --needed gcc git make flex bison gperf python-pip python-pyserial cmake ninja ccache
+ sudo pacman -S --needed gcc git make flex bison gperf python-pip python-pyserial cmake ninja ccache dfu-util
.. note::
使用 ESP-IDF 需要 CMake 3.5 或以上版本。较早版本的 Linux 可能需要升级才能向后移植仓库,或安装 "cmake3" 软件包,而不是安装 "cmake"。
diff --git a/docs/zh_CN/get-started/macos-setup-scratch.rst b/docs/zh_CN/get-started/macos-setup-scratch.rst
index f880317cd..68c0d5aba 100644
--- a/docs/zh_CN/get-started/macos-setup-scratch.rst
+++ b/docs/zh_CN/get-started/macos-setup-scratch.rst
@@ -31,11 +31,11 @@ MacPorts 需要完整的 XCode 软件,而 homebrew 只需要安装 XCode 命
- 若有 HomeBrew,您可以运行::
- brew install cmake ninja
+ brew install cmake ninja dfu-util
- 若有 MacPorts,您可以运行::
- sudo port install cmake ninja
+ sudo port install cmake ninja dfu-util
从源代码编译工具链
=================================
diff --git a/docs/zh_CN/get-started/macos-setup.rst b/docs/zh_CN/get-started/macos-setup.rst
index 4e9322550..c5d3e52bd 100644
--- a/docs/zh_CN/get-started/macos-setup.rst
+++ b/docs/zh_CN/get-started/macos-setup.rst
@@ -21,11 +21,11 @@ ESP-IDF 将使用 Mac OS 上默认安装的 Python 版本。
- 若有 HomeBrew_,您可以运行::
- brew install cmake ninja
+ brew install cmake ninja dfu-util
- 若有 MacPorts_,您可以运行::
- sudo port install cmake ninja
+ sudo port install cmake ninja dfu-util
- 若以上均不适用,请访问 CMake_ 和 Ninja_ 主页,查询有关 Mac OS 平台的下载安装问题。
diff --git a/tools/ci/config/host-test.yml b/tools/ci/config/host-test.yml
index 84ec1cee8..e7018ddc1 100644
--- a/tools/ci/config/host-test.yml
+++ b/tools/ci/config/host-test.yml
@@ -304,3 +304,11 @@ test_sysviewtrace_proc:
script:
- cd ${IDF_PATH}/tools/esp_app_trace/test/sysview
- ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test.sh
+
+test_mkdfu:
+ extends: .host_test_template
+ variables:
+ LC_ALL: C.UTF-8
+ script:
+ - cd ${IDF_PATH}/tools/test_mkdfu
+ - ${IDF_PATH}/tools/ci/multirun_with_pyenv.sh ./test_mkdfu.py
diff --git a/tools/ci/executable-list.txt b/tools/ci/executable-list.txt
index 690ba367f..426e30851 100644
--- a/tools/ci/executable-list.txt
+++ b/tools/ci/executable-list.txt
@@ -85,12 +85,14 @@ tools/ldgen/ldgen.py
tools/ldgen/test/test_fragments.py
tools/ldgen/test/test_generation.py
tools/mass_mfg/mfg_gen.py
+tools/mkdfu.py
tools/set-submodules-to-github.sh
tools/test_check_kconfigs.py
tools/test_idf_monitor/run_test_idf_monitor.py
tools/test_idf_py/test_idf_py.py
tools/test_idf_size/test.sh
tools/test_idf_tools/test_idf_tools.py
+tools/test_mkdfu/test_mkdfu.py
tools/unit-test-app/tools/get_available_configs.sh
tools/unit-test-app/unit_test.py
tools/windows/eclipse_make.sh
diff --git a/tools/ci/test_build_system_cmake.sh b/tools/ci/test_build_system_cmake.sh
index 37cbc7902..48a156a2f 100755
--- a/tools/ci/test_build_system_cmake.sh
+++ b/tools/ci/test_build_system_cmake.sh
@@ -707,6 +707,16 @@ endmenu\n" >> ${IDF_PATH}/Kconfig
bin_header_match build/bootloader/bootloader.bin "021f"
rm sdkconfig
+ print_status "DFU build works"
+ rm -f -r build sdkconfig
+ idf.py dfu &> tmp.log
+ grep "command \"dfu\" is not known to idf.py and is not a Ninja target" tmp.log || (tail -n 100 tmp.log ; failure "DFU build should fail for default chip target")
+ idf.py set-target esp32s2
+ idf.py dfu &> tmp.log
+ grep "build/dfu.bin\" has been written. You may proceed with DFU flashing." tmp.log || (tail -n 100 tmp.log ; failure "DFU build should succeed for esp32s2")
+ rm tmp.log
+ assert_built ${APP_BINS} ${BOOTLOADER_BINS} ${PARTITION_BIN} "dfu.bin"
+
print_status "All tests completed"
if [ -n "${FAILURES}" ]; then
echo "Some failures were detected:"
diff --git a/tools/cmake/dfu.cmake b/tools/cmake/dfu.cmake
new file mode 100644
index 000000000..f29de4b58
--- /dev/null
+++ b/tools/cmake/dfu.cmake
@@ -0,0 +1,26 @@
+# Add DFU build and flashing related targets
+#
+
+function(__add_dfu_targets)
+ idf_build_get_property(target IDF_TARGET)
+ if(NOT "${target}" STREQUAL "esp32s2")
+ return()
+ endif()
+
+ idf_build_get_property(python PYTHON)
+ idf_build_get_property(idf_path IDF_PATH)
+
+ add_custom_target(dfu
+ COMMAND ${python} ${idf_path}/tools/mkdfu.py write
+ -o "${CMAKE_CURRENT_BINARY_DIR}/dfu.bin"
+ --json "${CMAKE_CURRENT_BINARY_DIR}/flasher_args.json"
+ DEPENDS gen_project_binary bootloader
+ VERBATIM
+ USES_TERMINAL)
+
+ add_custom_target(dfu-flash
+ COMMAND dfu-util
+ -D "${CMAKE_CURRENT_BINARY_DIR}/dfu.bin"
+ VERBATIM
+ USES_TERMINAL)
+endfunction()
diff --git a/tools/cmake/idf.cmake b/tools/cmake/idf.cmake
index 2d396c886..82468355e 100644
--- a/tools/cmake/idf.cmake
+++ b/tools/cmake/idf.cmake
@@ -43,6 +43,7 @@ if(NOT __idf_env_set)
include(utilities)
include(targets)
include(ldgen)
+ include(dfu)
include(version)
__build_init("${idf_path}")
diff --git a/tools/cmake/project.cmake b/tools/cmake/project.cmake
index 11b6c7916..2ec9ea4a6 100644
--- a/tools/cmake/project.cmake
+++ b/tools/cmake/project.cmake
@@ -486,6 +486,9 @@ macro(project project_name)
unset(idf_size)
+ # Add DFU build and flash targets
+ __add_dfu_targets()
+
idf_build_executable(${project_elf})
__project_info("${test_components}")
diff --git a/tools/idf.py b/tools/idf.py
index 39716210d..118ff5c30 100755
--- a/tools/idf.py
+++ b/tools/idf.py
@@ -447,7 +447,7 @@ def init_cli(verbose_output=None):
def _print_closing_message(self, args, actions):
# print a closing message of some kind
#
- if "flash" in str(actions):
+ if "flash" in str(actions) or "dfu" in str(actions):
print("Done")
return
diff --git a/tools/idf_py_actions/dfu_ext.py b/tools/idf_py_actions/dfu_ext.py
new file mode 100644
index 000000000..af8d2d7fa
--- /dev/null
+++ b/tools/idf_py_actions/dfu_ext.py
@@ -0,0 +1,39 @@
+from idf_py_actions.tools import is_target_supported, ensure_build_directory, run_target
+from idf_py_actions.errors import FatalError
+
+
+def action_extensions(base_actions, project_path):
+
+ SUPPORTED_TARGETS = ['esp32s2']
+
+ def dfu_target(target_name, ctx, args):
+ ensure_build_directory(args, ctx.info_name)
+ run_target(target_name, args)
+
+ def dfu_flash_target(target_name, ctx, args):
+ ensure_build_directory(args, ctx.info_name)
+
+ try:
+ run_target(target_name, args)
+ except FatalError:
+ # Cannot capture the error from dfu-util here so the best advise is:
+ print('Please have a look at the "Device Firmware Upgrade through USB" chapter in API Guides of the '
+ 'ESP-IDF documentation for solving common dfu-util issues.')
+ raise
+
+ dfu_actions = {
+ "actions": {
+ "dfu": {
+ "callback": dfu_target,
+ "short_help": "Build the DFU binary",
+ "dependencies": ["all"],
+ },
+ "dfu-flash": {
+ "callback": dfu_flash_target,
+ "short_help": "Flash the DFU binary",
+ "order_dependencies": ["dfu"],
+ },
+ }
+ }
+
+ return dfu_actions if is_target_supported(project_path, SUPPORTED_TARGETS) else {}
diff --git a/tools/idf_py_actions/tools.py b/tools/idf_py_actions/tools.py
index 13cf71211..788bcca5e 100644
--- a/tools/idf_py_actions/tools.py
+++ b/tools/idf_py_actions/tools.py
@@ -270,6 +270,13 @@ def get_sdkconfig_value(sdkconfig_file, key):
return value
+def is_target_supported(project_path, supported_targets):
+ """
+ Returns True if the active target is supported, or False otherwise.
+ """
+ return get_sdkconfig_value(os.path.join(project_path, "sdkconfig"), 'CONFIG_IDF_TARGET') in supported_targets
+
+
def _guess_or_check_idf_target(args, prog_name, cache):
"""
If CMakeCache.txt doesn't exist, and IDF_TARGET is not set in the environment, guess the value from
diff --git a/tools/mkdfu.py b/tools/mkdfu.py
new file mode 100755
index 000000000..d2e562c71
--- /dev/null
+++ b/tools/mkdfu.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+#
+# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# This program creates archives compatible with ESP32-S* ROM DFU implementation.
+#
+# The archives are in CPIO format. Each file which needs to be flashed is added to the archive
+# as a separate file. In addition to that, a special index file, 'dfuinfo0.dat', is created.
+# This file must be the first one in the archive. It contains binary structures describing each
+# subsequent file (for example, where the file needs to be flashed/loaded).
+
+from collections import namedtuple
+from future.utils import iteritems
+import argparse
+import hashlib
+import json
+import os
+import struct
+import zlib
+
+try:
+ import typing
+except ImportError:
+ # Only used for type annotations
+ pass
+
+try:
+ from itertools import izip as zip
+except ImportError:
+ # Python 3
+ pass
+
+# CPIO ("new ASCII") format related things
+CPIO_MAGIC = b"070701"
+CPIO_STRUCT = b"=6s" + b"8s" * 13
+CPIOHeader = namedtuple(
+ "CPIOHeader",
+ [
+ "magic",
+ "ino",
+ "mode",
+ "uid",
+ "gid",
+ "nlink",
+ "mtime",
+ "filesize",
+ "devmajor",
+ "devminor",
+ "rdevmajor",
+ "rdevminor",
+ "namesize",
+ "check",
+ ],
+)
+CPIO_TRAILER = "TRAILER!!!"
+
+
+def make_cpio_header(
+ filename_len, file_len, is_trailer=False
+): # type: (int, int, bool) -> CPIOHeader
+ """ Returns CPIOHeader for the given file name and file size """
+
+ def as_hex(val): # type: (int) -> bytes
+ return "{:08x}".format(val).encode("ascii")
+
+ hex_0 = as_hex(0)
+ mode = hex_0 if is_trailer else as_hex(0o0100644)
+ nlink = as_hex(1) if is_trailer else hex_0
+ return CPIOHeader(
+ magic=CPIO_MAGIC,
+ ino=hex_0,
+ mode=mode,
+ uid=hex_0,
+ gid=hex_0,
+ nlink=nlink,
+ mtime=hex_0,
+ filesize=as_hex(file_len),
+ devmajor=hex_0,
+ devminor=hex_0,
+ rdevmajor=hex_0,
+ rdevminor=hex_0,
+ namesize=as_hex(filename_len),
+ check=hex_0,
+ )
+
+
+# DFU format related things
+# Structure of one entry in dfuinfo0.dat
+DFUINFO_STRUCT = b" int
+ """ Calculate CRC32/JAMCRC of data, with an optional initial value """
+ uint32_max = 0xFFFFFFFF
+ return uint32_max - (zlib.crc32(data, crc) & uint32_max)
+
+
+def pad_bytes(b, multiple, padding=b"\x00"): # type: (bytes, int, bytes) -> bytes
+ """ Pad 'b' to a length divisible by 'multiple' """
+ padded_len = (len(b) + multiple - 1) // multiple * multiple
+ return b + padding * (padded_len - len(b))
+
+
+class EspDfuWriter(object):
+ def __init__(self, dest_file): # type: (typing.BinaryIO) -> None
+ self.dest = dest_file
+ self.entries = [] # type: typing.List[bytes]
+ self.index = [] # type: typing.List[DFUInfo]
+
+ def add_file(self, flash_addr, path): # type: (int, str) -> None
+ """ Add file to be written into flash at given address """
+ with open(path, "rb") as f:
+ self._add_cpio_flash_entry(os.path.basename(path), flash_addr, f.read())
+
+ def finish(self): # type: () -> None
+ """ Write DFU file """
+ # Prepare and add dfuinfo0.dat file
+ dfuinfo = b"".join([struct.pack(DFUINFO_STRUCT, *item) for item in self.index])
+ self._add_cpio_entry(DFUINFO_FILE, dfuinfo, first=True)
+
+ # Add CPIO archive trailer
+ self._add_cpio_entry(CPIO_TRAILER, b"", trailer=True)
+
+ # Combine all the entries and pad the file
+ out_data = b"".join(self.entries)
+ cpio_block_size = 10240
+ out_data = pad_bytes(out_data, cpio_block_size)
+
+ # Add DFU suffix and CRC
+ out_data += struct.pack(DFUSUFFIX_STRUCT, *DFUSUFFIX_DEFAULT)
+ out_data += struct.pack(DFUCRC_STRUCT, dfu_crc(out_data))
+
+ # Finally write the entire binary
+ self.dest.write(out_data)
+
+ def _add_cpio_flash_entry(
+ self, filename, flash_addr, data
+ ): # type: (str, int, bytes) -> None
+ md5 = hashlib.md5()
+ md5.update(data)
+ self.index.append(
+ DFUInfo(
+ address=flash_addr,
+ flags=0,
+ name=filename.encode("utf-8"),
+ md5=md5.digest(),
+ )
+ )
+ self._add_cpio_entry(filename, data)
+
+ def _add_cpio_entry(
+ self, filename, data, first=False, trailer=False
+ ): # type: (str, bytes, bool, bool) -> None
+ filename_b = filename.encode("utf-8") + b"\x00"
+ cpio_header = make_cpio_header(len(filename_b), len(data), is_trailer=trailer)
+ entry = pad_bytes(
+ struct.pack(CPIO_STRUCT, *cpio_header) + filename_b, 4
+ ) + pad_bytes(data, 4)
+ if not first:
+ self.entries.append(entry)
+ else:
+ self.entries.insert(0, entry)
+
+
+def action_write(args):
+ writer = EspDfuWriter(args['output_file'])
+ for addr, f in args['files']:
+ print('Adding {} at {:#x}'.format(f, addr))
+ writer.add_file(addr, f)
+ writer.finish()
+ print('"{}" has been written. You may proceed with DFU flashing.'.format(args['output_file'].name))
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ # Provision to add "info" command
+ subparsers = parser.add_subparsers(dest="command")
+ write_parser = subparsers.add_parser("write")
+ write_parser.add_argument("-o", "--output-file",
+ help='Filename for storing the output DFU image',
+ required=True,
+ type=argparse.FileType("wb"))
+ write_parser.add_argument("--json",
+ help='Optional file for loading "flash_files" dictionary with items')
+ write_parser.add_argument("files",
+ metavar=" ", help='Add at ',
+ nargs="*")
+
+ args = parser.parse_args()
+
+ def check_file(file_name):
+ if not os.path.isfile(file_name):
+ raise RuntimeError('{} is not a regular file!'.format(file_name))
+ return file_name
+
+ files = []
+ if args.files:
+ files += [(int(addr, 0), check_file(f_name)) for addr, f_name in zip(args.files[::2], args.files[1::2])]
+
+ if args.json:
+ json_dir = os.path.dirname(os.path.abspath(args.json))
+
+ def process_json_file(path):
+ '''
+ The input path is relative to json_dir. This function makes it relative to the current working
+ directory.
+ '''
+ return check_file(os.path.relpath(os.path.join(json_dir, path), start=os.curdir))
+
+ with open(args.json) as f:
+ files += [(int(addr, 0),
+ process_json_file(f_name)) for addr, f_name in iteritems(json.load(f)['flash_files'])]
+
+ files = sorted([(addr, f_name) for addr, f_name in iteritems(dict(files))],
+ key=lambda x: x[0]) # remove possible duplicates and sort based on the address
+
+ cmd_args = {'output_file': args.output_file,
+ 'files': files,
+ }
+
+ {'write': action_write
+ }[args.command](cmd_args)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/test_mkdfu/1/1.bin b/tools/test_mkdfu/1/1.bin
new file mode 100644
index 000000000..60320aa0d
Binary files /dev/null and b/tools/test_mkdfu/1/1.bin differ
diff --git a/tools/test_mkdfu/1/2.bin b/tools/test_mkdfu/1/2.bin
new file mode 100644
index 000000000..1b9950f09
Binary files /dev/null and b/tools/test_mkdfu/1/2.bin differ
diff --git a/tools/test_mkdfu/1/3.bin b/tools/test_mkdfu/1/3.bin
new file mode 100644
index 000000000..08369e849
Binary files /dev/null and b/tools/test_mkdfu/1/3.bin differ
diff --git a/tools/test_mkdfu/1/dfu.bin b/tools/test_mkdfu/1/dfu.bin
new file mode 100644
index 000000000..74f9fef52
Binary files /dev/null and b/tools/test_mkdfu/1/dfu.bin differ
diff --git a/tools/test_mkdfu/1/flasher_args.json b/tools/test_mkdfu/1/flasher_args.json
new file mode 100644
index 000000000..06a54d0cc
--- /dev/null
+++ b/tools/test_mkdfu/1/flasher_args.json
@@ -0,0 +1,7 @@
+{
+ "flash_files" : {
+ "0x8000" : "2.bin",
+ "0x1000" : "1.bin",
+ "0x10000" : "3.bin"
+ }
+}
diff --git a/tools/test_mkdfu/test_mkdfu.py b/tools/test_mkdfu/test_mkdfu.py
new file mode 100755
index 000000000..1ce60c227
--- /dev/null
+++ b/tools/test_mkdfu/test_mkdfu.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020 Espressif Systems (Shanghai) CO LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import unicode_literals
+import filecmp
+import os
+import pexpect
+import shutil
+import sys
+import tempfile
+import time
+import unittest
+
+current_dir = os.path.dirname(os.path.realpath(__file__))
+mkdfu_path = os.path.join(current_dir, '..', 'mkdfu.py')
+
+
+class TestHelloWorldExample(unittest.TestCase):
+ def common_test(self, add_args):
+ with tempfile.NamedTemporaryFile(delete=False) as f:
+ self.addCleanup(os.unlink, f.name)
+ cmd = ' '.join([sys.executable, mkdfu_path, 'write',
+ '-o', f.name,
+ add_args])
+ p = pexpect.spawn(cmd, timeout=10)
+ self.addCleanup(p.terminate, force=True)
+
+ p.expect_exact(['Adding 1/bootloader.bin at 0x1000',
+ 'Adding 1/partition-table.bin at 0x8000',
+ 'Adding 1/hello-world.bin at 0x10000',
+ '"{}" has been written. You may proceed with DFU flashing.'.format(f.name)])
+
+ # Need to wait for the process to end because the output file is closed when mkdfu exits.
+ # Do non-blocking wait instead of the blocking p.wait():
+ for _ in range(10):
+ if not p.isalive():
+ break
+ time.sleep(0.5)
+ else:
+ p.terminate()
+
+ self.assertTrue(filecmp.cmp(f.name, os.path.join(current_dir, '1','dfu.bin')), 'Output files are different')
+
+ def test_with_json(self):
+ self.common_test(' '.join(['--json', os.path.join(current_dir, '1', 'flasher_args.json')]))
+
+ def test_without_json(self):
+
+ self.common_test(' '.join(['0x1000', os.path.join(current_dir, '1', '1.bin'),
+ '0x8000', os.path.join(current_dir, '1', '2.bin'),
+ '0x10000', os.path.join(current_dir, '1', '3.bin')
+ ]))
+
+ def test_filenames(self):
+ temp_dir = tempfile.mkdtemp(prefix='very_long_directory_name' * 8)
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+
+ with tempfile.NamedTemporaryFile(dir=temp_dir, delete=False) as f:
+ output = f.name
+
+ with tempfile.NamedTemporaryFile(prefix='ľščťžýáíéěř\u0420\u043e\u0441\u0441\u0438\u044f',
+ dir=temp_dir,
+ delete=False) as f:
+ bootloader = f.name
+
+ shutil.copyfile(os.path.join(current_dir, '1', '1.bin'), bootloader)
+
+ cmd = ' '.join([sys.executable, mkdfu_path, 'write',
+ '-o', output,
+ ' '.join(['0x1000', bootloader,
+ '0x8000', os.path.join(current_dir, '1', '2.bin'),
+ '0x10000', os.path.join(current_dir, '1', '3.bin')
+ ])
+ ])
+ p = pexpect.spawn(cmd, timeout=10, encoding='utf-8')
+ self.addCleanup(p.terminate, force=True)
+
+ p.expect_exact(['Adding {} at 0x1000'.format(bootloader),
+ 'Adding 1/2.bin at 0x8000',
+ 'Adding 1/3.bin at 0x10000',
+ '"{}" has been written. You may proceed with DFU flashing.'.format(output)])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/tools.json b/tools/tools.json
index f1c93c2f6..840a734e6 100644
--- a/tools/tools.json
+++ b/tools/tools.json
@@ -483,6 +483,43 @@
}
}
]
+ },
+ {
+ "description": "dfu-util (Device Firmware Upgrade Utilities)",
+ "export_paths": [
+ [
+ "dfu-util-0.9-win64"
+ ]
+ ],
+ "export_vars": {},
+ "info_url": "http://dfu-util.sourceforge.net/",
+ "install": "never",
+ "license": "GPL-2.0-only",
+ "name": "dfu-util",
+ "platform_overrides": [
+ {
+ "install": "always",
+ "platforms": [
+ "win64"
+ ]
+ }
+ ],
+ "version_cmd": [
+ "dfu-util",
+ "--version"
+ ],
+ "version_regex": "dfu-util ([0-9.]+)",
+ "versions": [
+ {
+ "name": "0.9",
+ "status": "recommended",
+ "win64": {
+ "sha256": "5816d7ec68ef3ac07b5ac9fb9837c57d2efe45b6a80a2f2bbe6b40b1c15c470e",
+ "size": 735635,
+ "url": "https://dl.espressif.com/dl/dfu-util-0.9-win64.zip"
+ }
+ }
+ ]
}
],
"version": 1