Updating device firmware using DFU in Zephyr Project on a FRDM-K64F board

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:

Zephyr Project - MCU boot

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:

  1. DFU application is running and waiting for commands from a host application
  2. Host application initiates an download operation, meaning that a firmware file is downloaded to the SLOT-2
  3. MCUboot is instructed that there is an test image (not permanent) in SLOT-2 by the DFU application
  4. Device resets/reboots
  5. MCUboot swaps the images between SLOT-1 & SLOT-2 meaning that DFU application ends up on SLOT-2 and our application ends up in SLOT-1
  6. MCUboot executes the code in SLOT-1 (our application)
  7. Device resets/reboots
  8. MCUboot swaps the images between SLOT-1 & SLOT-2 again (revert), meaning that the DFU application ends up in SLOT-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!

5 Likes

I have a question on the step number 4 where you mention to add certain flags which enables the device to automatically reboot. Being relatively new to this, I am not able to figure out where exactly do I need to add the config flags.Can you help me out with this?

Also once a firmware update is flashed and is running on the peripheral device, and let’s say I have another firmware to flash (basically an update). How do I make my device automatically come back into DFU mode and flash the (updated) firmware instead of the currently running firmware? As the device currently is running a firmware, I was wondering how to automatically trigger it to boot again in DFU mode. Hopefully this issue makes sense.

For reference, I want to try this out on an NRF5340 based system.

Any help on both the queries would be super helpful.