The Device Firmware Upgrade (DFU) library provides functionality to facilitate firmware upgrades over various transport mechanisms. It includes support for handling DFU packets, managing firmware images, and ensuring the integrity of the upgrade process.
lib_dfu is intended to be used with the
XCommon CMake
, the XMOS application build and dependency management system.
To use this library in an application include lib_dfu in the application’s APP_DEPENDENT_MODULES list in
CMakeLists.txt, for example:
set(APP_DEPENDENT_MODULES "lib_dfu")
Note
Dependent modules should be pinned to release versions where possible, otherwise the latest commit on the develop branch will be used. For further details on managing modules, pinning to a release version and other options, please see the page xcommon-cmake Dependency Management.
For flash memory support in an application, this library requires linking to the Tool’s Quad Flash library quadflash in
the application’s APP_COMPILER_FLAGS list in CMakeLists.txt, for example:
set(APP_COMPILER_FLAGS ... -lquadflash ...)
All lib_dfu functions can be accessed via the dfu.h header file, for example:
#include "dfu.h"
The library uses an optional header, dfu_conf.h, when present in the application allows the user to reconfigure the library.
For example the following configuration allows the user to define the device’s firmware version.
Please see the DFU Configuration Options section for details on all available configuration options.
#define DFU_BCD_DEVICE 0x0101
In lib_dfu, the DFU implementation is transport agnostic, and can be used with any physical transport by
implementing the necessary transport layer to receive DFU commands and send them to the DFU task.
This library is intended for a host device to update a target device, so the DFU implementation is designed to be used in the target device, and a separate host application will need to be used to send the DFU commands from the host to the target device.
For DFU over USB, the DFU implementation in lib_dfu is compliant with version 1.1 of
Universal Serial Bus Device Class Specification for Device Firmware Upgrade.
This section describes the DFU implementation in lib_dfu.
For information about using a DFU loader to send DFU commands to a USB device, refer to app-note
AN02019: Using Device Firmware Upgrade (DFU) in USB Audio.
For detailed information on creating a USB device with DFU capabilities, please see lib_xua: Device Firmware Upgrade (DFU) over USB
For DFU over non-USB transports, the transport layer will need to be selected by the user, the example application in this library demonstrates how to implement DFU over I2C using lib_device_control.
This library uses the flash user library to manage the flash memory where the firmware images are stored, please see XMOS flash user library.
As seen in the flash library documentation the flash memory is divided into two sections, the factory image section and the upgrade image section. Under normal circumstances the factory image will remain present for the life of the device and is used as a fallback in case the upgrade image is invalid. The upgrade image is present after the new firmware is written during the DFU download process.
Table 1 lists a few important DFU related configuration options.
For full details of all configuration options please see the DFU Configuration Options section.
Define |
Description |
Default |
|---|---|---|
|
Enable DFU functionality for device applications, disable for host applications. |
|
|
Enable DFU over USB support. Disable for non-USB transports. |
|
|
Enable (non-USB) DFU over |
|
|
Device release number in binary-coded decimal (BCD) format. |
|
The DFU implementation in lib_dfu uses a number of threads to perform the DFU process.
For USB transports, there is the USB lib_xud thread, which handles the USB communication and passes it to
the endpoint0 thread, which are outlined in Fig. 1.
The endpoint0 thread receives the DFU commands from the host and sends them to the DFU task. Due to the
use of the i_dfu interface, the DFU task can be distributable and thus called directly from the endpoint0 thread,
so no additional threads are needed for the DFU task.
DFU thread diagram for USB transports
For non-USB transports, there is a thread for the physical transport layer, a thread for the
lib_device_control control client, and one thread for the
control server with the DFU task, which are outlined in Fig. 2. Three threads in total.
DFU thread diagram for non-USB transports
The main constraint to be aware of when designing DFU functionality into an application is that
the DFU task must be running on tile[0], as the flash memory is only accessible from tile 0.
For details on working with flash memory on XCORE devices, please see
Design and manufacture systems with flash memory.
The physical transport can be on either tile as there is an interface to bridge the communications.
The secondary constraint is the time taken to perform the DFU process, particularly the flash erase and write operations, which can take a a few minutes to complete with very large images (>512 kB).
The timing of the DFU process can be managed by setting the appropriate response values for the DFU status requests
managed through the configuration defines that start POLL_TIMEOUT_DNLOAD_, see DFU Configuration Options,
to allow the host to know how long to wait before sending the next command. Many of these values can be taken from the flash memory data-sheet.
The lifecycle of a product with DFU is an important consideration when designing the DFU functionality, as it can impact the user experience and the security of the device.
The main stages of the product are:
Manufacturing: During this stage, the device is produced and the factory firmware image is flashed onto the device. The DFU functionality should be tested during this stage to ensure that it is working correctly.
Deployment: During this stage, the device is shipped to the end-users. The DFU functionality should be available to allow for firmware updates and bug fixes.
Maintenance: During this stage, the device is in use and may require firmware updates to add new features or fix bugs. The DFU functionality should be reliable and secure to ensure that firmware updates can be performed without issues.
End of Life: During this stage, the device is no longer supported and may not receive firmware updates. The DFU functionality may still be present, but it may not be secure or reliable. This can also include decommissioning of the device, where the DFU functionality can be used to erase the firmware and render the device inoperable.
Carefully considering the DFU lifecycle, and designing the DFU functionality accordingly, can help ensure that the device is secure and provides a good user experience throughout its lifecycle.
Device security is an important consideration when implementing DFU functionality, as it involves allowing firmware updates which, if not properly secured, could be exploited by malicious actors.
Warning
No security is implemented in the DFU implementation in lib_dfu.
It is the responsibility of the user to implement necessary security measures in their application when using DFU functionality.
For information on securing XCORE devices, please see Safeguard IP and device authenticity.
Here are some security considerations to keep in mind when implementing DFU:
Authentication: Implement authentication mechanisms to ensure that only authorized users can perform firmware updates.
Encryption: Consider encrypting the firmware images to protect them from being tampered with or reverse engineered.
Access Control: Implement access control mechanisms to restrict who can initiate the DFU process.
Secure Boot: Implement a secure boot process that verifies the integrity of the firmware at startup.
Rollback Protection: Implement mechanisms to prevent rollback attacks, where an attacker tries to install an older, vulnerable version of the firmware.
Logging and Monitoring: Implement logging of DFU operations and monitor for any suspicious activity.
By carefully considering these security aspects and implementing appropriate measures, can help ensure that the DFU functionality in the user’s device is secure and resistant to potential attacks.
The DFU operations process is as follows:
The device starts in runtime mode, running the factory image or the upgrade image if it is valid.
The host sends a detach command to the device to switch it to DFU mode.
The device reboots into DFU mode and enumerates with the DFU descriptors.
The host sends DFU download or upload commands to write or read the firmware image to or from the device.
The host sends a bus-reset to the device to switch it back to runtime mode.
The DFU depends on the XCORE boot process, and the role of the flash loader to run the upgrade image when valid, for more details please see Design and manufacture systems with flash memory.
Before starting the DFU upload or download process, the host sends a DFU_DETACH command to detach the device from runtime to DFU mode,
see Fig. 3.
In response to the DFU_DETACH command, the device reboots itself into DFU mode and enumerates using the DFU mode descriptors.
For non-USB transports such as I2C, the device simply transfers into DFU mode without rebooting, after a pause the host can determine
the mode switch by querying the device for its descriptors.
Once the device is in DFU mode, the DFU interface can accept commands defined by the
DFU 1.1 class specification.
Message sequence chart for the DFU entry operation
After detaching the device, the host proceeds with the DFU download/upload commands to write/read the firmware upgrade image to/from the device.
During the DFU download process, on receiving the first DFU_DNLOAD command, the device starts to erase
FLASH_MAX_UPGRADE_SIZE bytes of the upgrade section of the flash, see Fig. 4. This is done by repeatedly calling the flash erase function until the entire upgrade section is erased,
and can take several seconds. To avoid the DFU_DNLOAD request timing out, the flash erase is instead done in the DFU_GETSTATUS handling
code. So, the device ends up returning the status as dfuDNBUSY several times while the flash
erase is in progress.
Message sequence chart for the DFU erase operation
Fig. 5 describes the DFU download process following the erase operation, where the device receives data blocks and writes pages to flash (flash page is typically 256 bytes).
Message sequence chart for the DFU download operation
Once the DFU download or upload process is complete, the host sends a bus-reset to the device to switch it back to runtime mode.
Note
Once a valid upgrade image is loaded in flash, on subsequent reboots, the device will boot from the upgrade image.
If the upgrade image is invalid, the factory image will be loaded.
To revert back to the factory image, send the command XMOS_DFU_REVERTFACTORY to erase the upgrade image.
For USB transport, the device will enumerate with different USB descriptors, depending on whether it is in runtime mode or DFU mode.
For non-USB transports there is an extended command to allow the host to query the device, XMOS_DFU_GET_DESCRIPTOR.
To emulate the behaviour of a USB host there are some additional commands defined in the dfu_cmd_request enum for non-USB transports, these are:
XMOS_DFU_BUS_RESET: For emulating bus/device reset
XMOS_DFU_GET_DESCRIPTOR: For emulating getting a descriptor
Most of the slow operations are handled during the hold-off time allowed for after the DFU_GETSTATUS request by the poll_timeout value response.
But there are a few other operations for non-USB transports to be aware of, these are described below.
Timings for XMOS_DFU_BUS_RESET, when transitioning to DFU mode, the host should hold off for at least 100ms
to allow the device to prepare the flash memory for the DFU process.
When transitioning to APP mode, the host should hold-off the next request for at least 500ms after sending the bus reset
command to allow the device to reboot and be ready for the next request.
Timings for XMOS_DFU_REVERTFACTORY, the host should hold-off the next request for at least 250ms after sending the command
to allow the device to erase the upgrade image and be ready for the next request.
This section assumes that the host compiler is installed and in the path, for details per host OS please see Host dependencies.
For Linux and Mac hosts, the host app can be built from a command terminal with the commands as shown:
cd lib_dfu/host
cmake -G "Unix Makefiles" -B build
xmake -j -C build
For Windows hosts the process is the same except the Ninja generator is recommended to be used with CMake and the executable will have a .exe extension.
The commands as shown:
cd lib_dfu/host
cmake -G "Ninja" -B build
cmake --build build
The built host application executable will be found in the bin subdirectory under each of the host directories.
For Windows hosts, the supported compiler is MSVC.
The Ninja build system is recommended to be used with CMake, but it is not required.
The libusb library is provided in the library for applications such as xmosdfu.
For Linux hosts, including Raspberry Pi, the supported compiler is GCC.
The libusb-1.0-0-dev package must be installed for USB transport.
For OSX hosts, the supported compiler is Clang. The libusb library is provided in the library for applications such as xmosdfu.
This application provides a fully featured DFU host implementation over I2C using lib_device_control to send control commands.
Note
The application is only supported on a Linux host with I2C, such as a Raspberry Pi.
The application supports DFU with the following command-line arguments:
--help: Show help message and exit
detach_and_bus_reset: Send the detach command followed by a bus reset to switch the device from runtime to DFU mode
reboot: Send a bus reset to switch the device from DFU to runtime mode
write_upgrade <boot.dfu>: Write the specified firmware image to the device using DFU download commands, the file must include the DFU suffix.
upload <file.bin>: Read the firmware image from the device using DFU upload commands and save it to the specified file, the file will be binary and not include the DFU suffix.
revert_factory: Revert back to the factory image by erasing the first flash sector of the upgrade image.
The I2C address can be configured using --i2c-address <address> argument, which defaults to 0x2C.
This is host app to generate the suffix of the firmware image in flash, this is used in the DFU download process to write the suffix to flash after writing the firmware image.
Note
The USB DFU specification requires a valid suffix to be present in the firmware image.
Before generating a suffixed binary file, the xe file will need to be processed using xflash.
After building the example...
cd examples/i2c/device
xflash --factory-version 15.3 --upgrade 1 bin/update/i2c_update.xe -o bin/update/i2c_update.bin
After extracting the binary file, a suffixed binary file can be generated using the suffix_generator
application with the following command-line arguments:
cd lib_dfu/host/suffix_generator
./bin/dfu_suffix_generator <VID> <PID> <bcdDevice> <input_binary> <output_file>
So for example the following would generate a suffixed binary file with the VID, PID and bcdDevice values for the example application:
./bin/dfu_suffix_generator 0x20b1 0x1234 0x0101 i2c_update.bin i2c_update_suffixed.bin
It is recommended to update the bcdDevice value for each new firmware version to allow the host to easily identify different versions of the firmware.
This is host library can be used to verify the suffix of the firmware image in flash is correct, Host dfu_i2c application uses this to verify firmware images before upgrading the device.
xmosdfu is a USB DFU host application that can be used to perform DFU operations such as firmware upgrades on XMOS devices over USB.
It uses the libusb library to communicate with the device over USB and provides a command-line interface.
For detailed instructions on using xmosdfu, please see AN02019: Using Device Firmware Upgrade (DFU) in USB Audio.
This section assumes that the XMOS XTC Tools have been
downloaded and installed. The required version is specified in the accompanying README.
Installation instructions can be found here.
Special attention should be paid to the section on Installation of Required Third-Party Tools.
The application is built using the xcommon-cmake build system, which is provided with the XTC tools and is based on CMake.
The lib_dfu software ZIP package should be downloaded and extracted to a chosen working
directory.
To configure the build, the following commands should be run from an XTC command prompt:
cd lib_dfu/examples/i2c/device
cmake -G "Unix Makefiles" -B build
If any dependencies are missing they will be retrieved automatically during this step.
The application binaries should then be built using xmake:
xmake -j -C build
Binary artifacts (.xe files) will be generated under the appropriate subdirectories of the
examples/i2c/device/bin directory — one for each supported build configuration.
For subsequent builds, the cmake step may be omitted.
If CMakeLists.txt or other build files are modified, cmake will be re-run automatically
by xmake as needed.
This is very similar to building the device example, please follow the build instructions in the
Building the example section to build the host application, except the host example is in the
examples/i2c/host_xcore directory.
The example is XCORE to XCORE over I2C, so two XCORE boards are needed. The example was developed and tested using the XK-EVK-XU316 and the XK-VOICE-L71.
Connect three jumper wires between the XK-EVK-XU316 and the XK-VOICE-L71 to allow the I2C communication between the host and device.
For the XK-EVK-XU316, connect the jumper wires as shown in the Fig. 6 and Fig. 7, connecting the I2C SCL, SDA and GND pins to the corresponding pins on the XK-VOICE-L71.
XK-EVK-XU316 I2C Hardware setup
The XK-VOICE-L71 uses the Raspberry Pi GPIO pins for I2C communication. For more information, refer to the
Raspberry Pi GPIO Documentation.
Connecting I2C between the XK-EVK-XU316 and XK-VOICE-L71
To run the example, connect a USB cable to power the XK-VOICE-L71 board as shown in Fig. 8,
and plug the XTAG to the board and connect the XTAG USB cable to the development machine. And also connect two
USB cables to the XK-EVK-XU316 to power it and allow programming and xscope communication.
XK-VOICE-L71 USB Hardware setup
When running two XTAGs connected to the development machine, one for the host and one for the device, the XTAG adapter ID will need to be provided to xrun to specify which XTAG to use for the host and which one to use for the device. The XTAG adapter ID’s can be found using the xrun command from an XTC command prompt. An example output is shown below:
$ xrun -l
Available XMOS Devices
----------------------
ID Name Adapter ID Devices
-- ---- ---------- -------
0 XMOS XTAG-4 ABCDEFGH XS3A[0]
1 XMOS XTAG-4 IJKLMNOP XS3A[0]
The device example needs to be running, otherwise the host will fail to connect to the device.
Note
The example is not a fully featured DFU implementation, it is designed to show the DFU detach process from runtime to DFU mode, so the device example will start in runtime mode, and only switch to DFU mode after receiving the detach command from the host.
From an XTC command prompt, the following command should be run from the examples/i2c/device directory:
xrun --xscope --adapter-id <device_xtag_adapter_id> ./bin/i2c.xe
Alternatively, the application can be programmed into flash memory for standalone execution:
xflash --adapter-id <device_xtag_adapter_id> ./bin/i2c.xe
From an XTC command prompt, the following command should be run from the examples/i2c/host_xcore directory:
xrun --xscope --adapter-id <host_xtag_adapter_id> ./bin/host_i2c_xcore.xe
The output from the host shows the DFU detach process, followed by a simple control data exchange between the host and device to show that the communication is working as expected.
xrun --xscope --adapter-id <host_xtag_adapter_id> ./bin/host_i2c_xcore.xe
i2c ready
Starting Control DFU example
DFU state: 0
DFU status: 0, timeout: 0 ms, next state: 0
Sent detach command
DFU status: 0, timeout: 0 ms, next state: 1
Sent bus reset command
DFU status: 0, timeout: 0 ms, next state: 2
control write command 9 failed with 5
Sent bus reset command
DFU status: 0, timeout: 0 ms, next state: 0
Starting Control data example
Written and read back command with payload: 0x00
Written and read back command with payload: 0x01
Written and read back command with payload: 0x02
Written and read back command with payload: 0x03
done
See Usage section and Example application for details on usage of the following.
DFU state machine states, from USB DFU spec v1.1
Values:
DFU device status code, from USB DFU spec v1.1
Values:
DFU request types
Largely follows the USB DFU class specification requests, with some additional custom requests for non-USB transports and deferred actions. Deferred action requests are not actual requests sent by the host, but are used internally to indicate slow operations to carry out after responding to the initial request, such as flash programming and rebooting, which cannot be completed within the time constraints of a single request/response transaction.
Values:
For emulating bus/device reset on transports other than USB.
For emulating getting a descriptor on transports other than USB.
Additional command that erases only the first flash sector of the upgrade image, to revert to the factory image
API function return values
Values:
DFU request response structure return type
Public Members
The status of the DFU request
The length of the data returned by the DFU request, only used for read operations.
The deferred DFU request, if any, used to action slow operations.
Functions
DFU request handling with arguments
request – the DFU request to handle
block – pointer to the data payload of the request for read/write operations, usage depends on command, for download it’s the data block to write, for upload it’s the buffer to fill with the data block read from flash, for other commands it’s unused and can be null
block_size_bytes – the size of the data block to read/write for upload/download commands, for other commands it’s unused and can be 0
block_num – for download command, the block number to write, for other commands it’s unused and can be null
DFU_API_SUCCESS – if command was handled successfully, the value is the upload block-number for upload command, 0 otherwise.
DFU_API_ERROR – if there was an error handling the command
DFU_API_BAD_PARAM – if the command or parameters were invalid
struct dfu_cmd_response containing status and any return value, usage depends on command, for upload the return_data_len is the size of the block to upload, for other commands it’s unused and can be 0, the deferred_request field is used to indicate any slow operations to carry out after responding to the request, such as flash programming and rebooting, which cannot be completed within the time constraints of a single request/response transaction.
Send request to DFU with no data
request – the DFU request to send
DFU_API_SUCCESS – for status, if command was handled successfully
DFU_API_ERROR – for status, if there was an error handling the command
DFU_API_BAD_PARAM – for status, if the command or parameters were invalid
struct dfu_cmd_response identical to dfu_request_with_arguments return value.
Defines
Main control for the function of the DFU library. When enabled, the application must provide “-lquadflash” to link the flash library. When disabled will include empty stubs for the API functions. These stubs are weak and can be overridden locally.
Main control for the USB functionality of the DFU library. When enabled, the DFU library will include USB support. When disabled, USB support will be excluded. Other transports can be used.
Note
Requires lib_xud or lib_xua to be added to APP_DEPENDENT_MODULES to use.
USB device release number in binary-coded decimal (BCD) format
Used in DFU for identifying the device version. The default value is 0x0100, which corresponds to version 1.00.
Reported to the host via the USB device descriptor when DFU_USB_EN is enabled. Or, when DFU_USB_EN is disabled, this can be accessed by the host using request XMOS_DFU_GET_DESCRIPTOR.
Optional, user defined flash device specification for DFU to use.
If not defined (default), the libquadflash will attempt to use JEDEC JESD216 Serial Flash Discoverable Parameters (SFDP) to identify the connected flash device and its capabilities. This requires the flash to be SFDP compliant, and may not work with all flash devices.
If DFU_USER_FLASH_DEVICE is defined, this should be defined as a fl_QuadDeviceSpec or fl_DeviceSpec structure, depending on whether DFU_QUAD_SPI_FLASH is set.
Whether DFU uses Quad SPI Flash, or older SPI flash (when 0)
Number of bytes transferred in each DFU packet on the given physical transport
Number of bytes in a flash page for the target device
Number of DFU packets per flash page
Whether to enable USB in-band functions for DFU Inband functions occur during the host request transaction. Out-of-band requests are deferred until after the host request has been completed, which require the DFU task to be allocated to a thread.
Defines flash area to erase on first DFU download request received
Flash library will round it up to the nearest sector, e.g. 4KB
Clock block for use by DFU flash operations
Poll timeout for DFU download entry in milliseconds, time taken to find the upgrade image, if it exists and erase the first sector.
The first sector erase can often take significantly longer than subsequent erases, as it may require additional setup such as checking whether an upgrade image exists and its size.
Values shorter than the actual time taken may cause the host to timeout or cause clock-stretching if using I2C.
Poll timeout for DFU download erase in milliseconds, time taken to erase following sectors of the upgrade image, when it exists.
The value should be taken from the Flash memory datasheet, for erase operations.
Values shorter than the actual time taken may cause the host to timeout or cause clock-stretching if using I2C.
Poll timeout for DFU download first page write in milliseconds, time taken to prepare for the write and write the first page of the upgrade image.
The first page can often take significantly longer to write than subsequent pages, as it may require additional setup such as preparing the flash for writing.
Values shorter than the actual time taken may cause the host to timeout or cause clock-stretching if using I2C.
Poll timeout for DFU download write in milliseconds, time taken to write subsequent pages of the upgrade image.
The value should be taken from the Flash memory datasheet, for write operations.
Values shorter than the actual time taken may cause the host to timeout or cause clock-stretching if using I2C.
Poll timeout for DFU manifest in milliseconds, time taken to write the last page and finalise the write process.
The value should be taken from the Flash memory datasheet, for write operations.
Values shorter than the actual time taken may cause the host to timeout or cause clock-stretching if using I2C.
Main control for the DFU lib_control_device server functionality. When enabled, the DFU library will include the control server for non-USB transports. When disabled, the control server will be excluded.
Note
Requires lib_device_control to be added to APP_DEPENDENT_MODULES to use.