Introduction
USB Device Firmware Upgrade (DFU) is an official USB device class specification of the USB Implementers Forum.
It specifies a vendor and device independent way of updating the firmware of a USB device. The idea is to have only one vendor-independent firmware update tool as part of the operating system, which can then (given a particular firmware image) be downloaded into the device.
In this tutorial we will cover how use the Device Firmware Update (DFU) USB class in Zephyr Project using a FRDM-K64F board as target.
This tutorial is preparation work to use Mender to deploy an proxy deployment to a peripheral device, which will be the device that we prepare in this tutorial.
A lot of the steps outlined in this tutorial are already covered by the Zephyr Project - Quick start guide, the additions we make are steps to build MCUboot and the DFU application on the FRDM-K64F and how to use dfu-util
to flash an example application.
Prerequisites for this tutorial
It is assumed that you are running a Debian based Linux distribution (native or in a VM) with the following packages installed:
sudo apt install --no-install-recommends git cmake ninja-build gperf \
ccache dfu-util device-tree-compiler wget \
python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \
make gcc gcc-multilib g++-multilib libsdl2-dev dfu-utils
Also since this tutorial is targeting the FRDM-K64F board, it is a requirement to be able to test the provided instructions.
Overview
The Zephyr Project already provides support for DFU together with MCUboot as a first-stage bootloader.
MCUboot is a secure bootloader for 32-bit MCUs. The goal of MCUboot is to define a common infrastructure for the bootloader, system flash layout on microcontroller systems, and to provide a secure bootloader that enables easy software upgrade.
Further they device flash layout in our setup can be described with the following simplified model:
Where,
- MCUboot acts as a first-stage bootloader
- The DFU application acts as a “recovery application”, which you can jump in to if you need to update the application (SLOT-1)
- The Scratch area is used by MCUboot to store information while an update is in progress.
MCUboot provides the key features needed to securely be able to update firmware of a device, while DFU acts as the interface to transfer and flash an image. Some of the key features of MCUboot are:
- Definition of an image format
- Support for integrity checking of firmware
- Support for image signing and verification
- “Boot Swap” logic to switch between image slots in a secure way
Step 0 - Preparations
The Zephyr Project uses the west
meta tool to handle source code, but it can also be used to flash device with built firmware among other things.
Install west
:
pip3 install west
Step 1 - Getting the source code
Change directory to $HOME
cd $HOME
Initialize the Zephyr Project workspace:
west init zephyrproject && cd zephyrproject && west update
Install the python requirements of the Zephyr Project:
pip3 install -r ~/zephyrproject/zephyr/scripts/requirements.txt
Step 2 - Install Software Development Toolchain
Zephyr’s Software Development Kit (SDK) contains necessary Linux development tools to build Zephyr on all supported architectures. Additionally, it includes host tools such as custom QEMU binaries and a host compiler.
Change directory to $HOME
cd $HOME
Download the latest SDK as a self-extracting installation binary:
cd ~
wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.11.2/zephyr-sdk-0.11.2-setup.run
Run the installation binary, installing the SDK in your home folder $HOME/zephyr-sdk-0.11.2
:
chmod +x zephyr-sdk-0.11.2-setup.run
./zephyr-sdk-0.11.2-setup.run -- -d ~/zephyr-sdk-0.11.2
Set environment variables to let the build system know where to find the toolchain:
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=$HOME/zephyr-sdk-0.11.2
The SDK contains a udev rules file that provides information needed to identify boards and grant hardware access permission to flash tools. Install these udev rules with these commands:
sudo cp ${ZEPHYR_SDK_INSTALL_DIR}/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
sudo udevadm control --reload
Step 3 - Build and flash MCUboot
Change directory to $HOME/zephyrproject
cd $HOME/zephyrproject
Build MCUboot targeting frdm_k64f
:
west build -s $HOME/zephyrproject/bootloader/mcuboot/boot/zephyr -d build-mcuboot -b frdm_k64f
NOTE! MCUboot is compiled with verified boot enabled, which means that sub-sequent files that are flashed on the device must be signed. The default key provided by MCUboot is used, this is not a secure key as it is publicly available in the MCUboot git repository and is only intended to be used for evaluation.
For the next step, the frdm_k64f
board needs to be connected to the PC trough the OpenSDAv2 USB
interface (J26 connector on the frdm_k64f
board).
Flash MCUboot on the frdm_k64f
board:
west flash -d build-mcuboot
Step 4 - Build and flash DFU application
The following changes are optional, but these make sure that the DFU application automatically reboots once it has written a firmware image and without these changes one would need to manually reset the device for it to boot the flashed image.
diff --git a/samples/subsys/usb/dfu/prj.conf b/samples/subsys/usb/dfu/prj.conf
index ffc1e9080b..f6f47e51b0 100644
--- a/samples/subsys/usb/dfu/prj.conf
+++ b/samples/subsys/usb/dfu/prj.conf
@@ -11,3 +11,4 @@ CONFIG_LOG=y
CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y
CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y
CONFIG_BOOTLOADER_MCUBOOT=y
+CONFIG_REBOOT=y
diff --git a/subsys/usb/class/usb_dfu.c b/subsys/usb/class/usb_dfu.c
index a31bdf58ed..630d18cc59 100644
--- a/subsys/usb/class/usb_dfu.c
+++ b/subsys/usb/class/usb_dfu.c
@@ -52,6 +52,8 @@
#include <usb/class/usb_dfu.h>
#include <usb_descriptor.h>
+#include <power/reboot.h>
+
#define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(usb_dfu);
@@ -356,6 +358,7 @@ static void dfu_flash_write(u8_t *data, size_t len)
dfu_data.state = dfuERROR;
dfu_data.status = errWRITE;
}
+ sys_reboot(SYS_REBOOT_WARM);
} else {
dfu_data.state = dfuDNLOAD_IDLE;
}
Build DFU application:
west build -b frdm_k64f -d build-dfu zephyr/samples/subsys/usb/dfu
Create a signed DFU application binary:
$HOME/zephyrproject/bootloader/mcuboot/scripts/imgtool.py sign \
--key $HOME/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem \
--header-size 0x200 \
--align 8 \
--version 1.2 \
--slot-size 0x60000 \
build-dfu/zephyr/zephyr.bin \
signed-dfu-app.bin
Flash the signed DFU application binary to SLOT-1
:
pyocd flash -e sector -a 0x20000 -t k64f $HOME/zephyrproject/signed-dfu-app.bin
If you are connected to the frdm_k64f
serial debug port you should see something similar to the following:
*** Booting Zephyr OS build v2.2.0-rc3-69-g5f0650b596c5 *** [00:00:00.005,000] <inf> mcuboot: Starting bootloader [00:00:00.011,000] <inf> mcuboot: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3 [00:00:00.021,000] <inf> mcuboot: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3 [00:00:00.030,000] <inf> mcuboot: Boot source: primary slot [00:00:00.038,000] <inf> mcuboot: Swap type: none [00:00:00.106,000] <inf> mcuboot: Bootloader chainload address offset: 0x20000 *** Booting Zephyr OS build v2.2.0-rc3-69-g5f0650b596c5 *** slot [00:00:00.007,000] <inf> main: This device supports USB DFU class.
This means that our device is now ready for transfer of firmware images using the DFU USB device class.
Step 5 - Build and download a sample application using DFU
We will be using the blinky
sample application as a reference to flash to our device using DFU. Prior to building the sample application we need to make sure that it is aware of MCUboot.
echo "CONFIG_BOOTLOADER_MCUBOOT=y" >> zephyr/samples/basic/blinky/prj.conf
MCUboot does not support executing position independent code which means that application is always booted from SLOT-1
and the application binaries must be linked in such a way that the start address is equal the start address of SLOT-1
. Will try to explain this further with a simple run trough of the flow when an DFU update is performed in our setup:
- DFU application is running and waiting for commands from a host application
- Host application initiates an
download
operation, meaning that a firmware file is downloaded to theSLOT-2
- MCUboot is instructed that there is an test image (not permanent) in
SLOT-2
by the DFU application - Device resets/reboots
- MCUboot swaps the images between
SLOT-1
&SLOT-2
meaning that DFU application ends up onSLOT-2
and our application ends up inSLOT-1
- MCUboot executes the code in
SLOT-1
(our application) - Device resets/reboots
- MCUboot swaps the images between
SLOT-1
&SLOT-2
again (revert), meaning that the DFU application ends up inSLOT-1
again and is started. This swap is done because in our reference setup we never make the application update permanent.
Build the blinky
sample application:
west build -b frdm_k64f -d build-blinky zephyr/samples/basic/blinky/
Create a signed blinky
application binary:
$HOME/zephyrproject/bootloader/mcuboot/scripts/imgtool.py sign \
--key $HOME/zephyrproject/bootloader/mcuboot/root-rsa-2048.pem \
--header-size 0x200 \
--align 8 \
--version 1.2 \
--slot-size 0x60000 \
build-blinky/zephyr/zephyr.bin \
signed-blinky-green.bin
Download the signed-blinky-green.bin
file to the device using DFU:
dfu-util --alt 1 --download signed-blinky-green.bin
NOTE! You need to manually restart the frdm_k64f
device to trigger the image swap in MCUboot.
If you are connected to the serial debug port you should see something like this:
*** Booting Zephyr OS build v2.2.0-rc3-69-g5f0650b596c5 *** [00:00:00.005,000] <inf> mcuboot: Starting bootloader [00:00:00.011,000] <inf> mcuboot: Primary image: magic=good, swap_type=0x4, copy_done=0x1, image_ok=0x1 [00:00:00.020,000] <inf> mcuboot: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3 [00:00:00.030,000] <inf> mcuboot: Boot source: none [00:00:00.035,000] <inf> mcuboot: Swap type: test [00:00:01.535,000] <inf> mcuboot: Bootloader chainload address offset: 0x20000 [00:00:01.543,000] <inf> mcuboot: Jumping to the first image slot0*** Booting Zephyr OS build v2.2.0-rc3-69-g5f0650b596c5 ***
NOTE! In this particular setup the updates are not permanent, which means that if you reset the device again it will enter the DFU application allowing you upload a different application binary.
Conclusion
In this tutorial we have gone trough how to setup secure and robust firmware updates on a MCU which allows to download images using the dfu-utils
. This essentially allows us to update firmware of the MCU using any Linux capable device, e.g from an embedded Linux device.
Further recommended reading:
If this tutorial was useful to you, please press like, or leave a thank you note to the contributor who put valuable time into this and made it available to you. It will be much appreciated!