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 ad7e8e49b..555234468 100755 --- a/tools/ci/test_build_system_cmake.sh +++ b/tools/ci/test_build_system_cmake.sh @@ -700,6 +700,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 663522b82..d96ee9f35 100644 --- a/tools/idf_py_actions/tools.py +++ b/tools/idf_py_actions/tools.py @@ -269,6 +269,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 index eecd00b55..d2e562c71 100755 --- a/tools/mkdfu.py +++ b/tools/mkdfu.py @@ -1,16 +1,31 @@ #!/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). -# -import argparse from collections import namedtuple +from future.utils import iteritems +import argparse import hashlib +import json import os import struct import zlib @@ -21,6 +36,12 @@ 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 @@ -166,46 +187,64 @@ class EspDfuWriter(object): def action_write(args): - writer = EspDfuWriter(args.output_file) - for addr, file in args.files: - writer.add_file(addr, file) + 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() - - -class WriteArgsAction(argparse.Action): - """ Helper for argparse to parse argument pairs """ - - def __init__(self, *args, **kwargs): - super(WriteArgsAction, self).__init__(*args, **kwargs) - - def __call__(self, parser, namespace, values, option_string=None): - # TODO: add validation - addr = 0 - result = [] - for i, value in enumerate(values): - if i % 2 == 0: - addr = int(value, 0) - else: - result.append((addr, value)) - - setattr(namespace, self.dest, result) + print('"{}" has been written. You may proceed with DFU flashing.'.format(args['output_file'].name)) def main(): - parser = argparse.ArgumentParser("mkdfu") + 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", type=argparse.FileType("wb")) - write_parser.add_argument( - "files", metavar="
", action=WriteArgsAction, nargs="+" - ) + 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() - print(repr(args)) - if args.command == "write": - action_write(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__": 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