Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

app: add USB Device Firmware Upgrade (DFU) support #28

Merged
merged 6 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
zephyr_include_directories(include)

add_subdirectory(subsys)

include(cmake/cannectivity.cmake)
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The CANnectivity firmware supports the following features, some of which depend
- CAN bus error reporting
- Automatic gs_usb driver loading under Linux using custom [udev rules](99-cannectivity.rules)
- Automatic WinUSB driver installation under Microsoft Windows 8.1 and newer
- USB Device Firmware Upgrade (DFU) mode

## Hardware Requirements

Expand Down Expand Up @@ -98,6 +99,38 @@ provided. These can be selected by setting either `FILE_SUFFIX=usbd_next` or

After building, the firmware can be flashed to the board by running the `west flash` command.

## USB Device Firmware Upgrade (DFU) Mode

CANnectivity supports USB Device Firmware Upgrade
([DFU](https://docs.zephyrproject.org/latest/services/device_mgmt/dfu.html)) via the
[MCUboot](https://www.trustedfirmware.org/projects/mcuboot/) bootloader. This is intended for use
with boards without an on-board programmer.

To build CANnectivity with MCUboot integration for USB DFU use
[sysbuild](https://docs.zephyrproject.org/latest/build/sysbuild/index.html) with the
`sysbuild-dfu.conf` configuration file when building for your board (here `frdm_k64f`):

```shell
west build -b frdm_k64f/mk64f12 --sysbuild ../custom/cannectivity/app/ -- -DSB_CONF_FILE=sysbuild-dfu.conf
```

After building, MCUboot and the CANnectivity firmware can be flashed to the board by running the
`west flash` command.

Subsequent CANnectivity firmware updates can be applied via USB DFU. In order to do so, the board
must first be booted into USB DFU mode. If your board has a dedicated DFU button (identified by the
`mcuboot-button0` devicetree alias) press and hold it for 5 seconds or press and hold the button
while powering up the board. If your board has a DFU LED (identified by the `mcuboot-led0`
devicetree alias), the LED will flash while the DFU button is being held and change to constant on
once DFU mode is activated. Refer to your board documentation for further details.

Once in DFU mode, the CANnectivity firmware can be updated using
[dfu-util](https://dfu-util.sourceforge.net/):

```shell
dfu-util -a 1 build/app/zephyr/zephyr.signed.bin.dfu
```

## CANnectivity as a Zephyr Module

The CANnectivity firmware repository is a [Zephyr
Expand Down
4 changes: 4 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ target_sources_ifdef(CONFIG_CANNECTIVITY_TIMESTAMP_COUNTER app PRIVATE
target_sources_ifdef(CONFIG_CANNECTIVITY_TERMINATION_GPIO app PRIVATE
src/termination.c
)

target_sources_ifdef(CONFIG_BOOTLOADER_MCUBOOT app PRIVATE
src/dfu.c
)
78 changes: 78 additions & 0 deletions app/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,84 @@ config CANNECTIVITY_TERMINATION_DEFAULT_ON
help
Enable CAN bus termination resistors on boot-up.

if BOOTLOADER_MCUBOOT

config CANNECTIVITY_DFU_BUTTON
bool "DFU button support"
default y
depends on $(dt_alias_enabled,mcuboot-button0)
select REBOOT
help
Enable support for rebooting into Device Firmware Upgrade (DFU) mode by holding the DFU
button (identified by the "mcuboot-button0" devicetree alias).

config CANNECTIVITY_DFU_BUTTON_HOLD_TIME
int "DFU button hold time in seconds"
depends on CANNECTIVITY_DFU_BUTTON
range 1 60
default 4
help
Number of seconds the Device Firmware Upgrade (DFU) button must be held to reboot into DFU
mode.

config CANNECTIVITY_DFU_LED
bool # hidden
default y
depends on $(dt_alias_enabled,mcuboot-led0)
help
Enable support for controlling the Device Firmware Upgrade (DFU) LED (identified by the
"mcuboot-led0" devicetree alias).

configdefault FLASH
default y

configdefault STREAM_FLASH
default y

configdefault FLASH_MAP
default y

configdefault IMG_MANAGER
default y

endif # BOOTLOADER_MCUBOOT

menuconfig CANNECTIVITY_GENERATE_USB_DFU_IMAGE
bool "Generate USB DFU image"
select BUILD_OUTPUT_BIN
help
Enabling this configuration allows automatic generation of an image with USB Device
Firmware Upgrade (DFU) suffix. This depends on the dfu-suffix utility from the dfu-util
software package.

if CANNECTIVITY_GENERATE_USB_DFU_IMAGE

config CANNECTIVITY_USB_DFU_VID
hex "USB DFU image Vendor ID (VID)"
default 0xffff
help
CANnectivity USB DFU image Vendor ID (VID).

config CANNECTIVITY_USB_DFU_PID
hex "USB DFU image Product ID (PID)"
default 0xffff
help
CANnectivity USB DFU image Product ID (PID).

config CANNECTIVITY_USB_DFU_DID
hex "USB DFU image Device ID"
default 0xffff
help
CANnectivity USB DFU image Device ID.

config CANNECTIVITY_USB_DFU_SPEC_ID
hex "USB DFU image Specification ID"
default 0x0100
help
CANnectivity USB DFU image Specification ID.

endif # CANNECTIVITY_GENERATE_USB_DFU_IMAGE

endmenu

source "Kconfig.zephyr"
46 changes: 46 additions & 0 deletions app/Kconfig.sysbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright (c) 2024 Henrik Brix Andersen <[email protected]>
# SPDX-License-Identifier: Apache-2.0

menuconfig CANNECTIVITY_USB_DFU
bool "CANnectivity USB DFU mode"
help
Enable support for CANnectivity USB Device Firmware Upgrade (DFU) mode using the MCUboot
bootloader.

if CANNECTIVITY_USB_DFU

config CANNECTIVITY_USB_DFU_MANUFACTURER
string "USB DFU mode manufacturer string"
default "CANnectivity"
help
CANnectivity USB DFU mode manufacturer string.

config CANNECTIVITY_USB_DFU_PRODUCT
string "USB DFU mode product string"
default "CANnectivity USB to CAN adapter in DFU mode"
help
CANnectivity USB DFU mode product string.

config CANNECTIVITY_USB_DFU_VID
hex "USB DFU mode Vendor ID (VID)"
default 0x1209
help
CANnectivity USB DFU mode Vendor ID (VID).

config CANNECTIVITY_USB_DFU_PID
hex "USB DFU mode Product ID (PID)"
default 0x0001
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PID is for testing only. Waiting on pidcodes/pidcodes.github.com#981

help
CANnectivity USB DFU mode Product ID (PID).

config CANNECTIVITY_USB_DFU_MAX_POWER
int "USB DFU mode maximum power"
default 125
range 0 250
help
CANnectivity USB DFU mode maximum current draw in milliampere (mA) divided by 2.
A value of 125 results in a maximum current draw value of 250 mA.

endif # CANNECTIVITY_USB_DFU

source "share/sysbuild/Kconfig"
6 changes: 6 additions & 0 deletions app/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,9 @@ tests:
integration_platforms:
- frdm_k64f
- lpcxpresso55s16
app.cannectivity.dfu:
sysbuild: true
depends_on: usb_device can
platform_allow:
- frdm_k64f
extra_args: SB_CONF_FILE=sysbuild-dfu.conf
7 changes: 7 additions & 0 deletions app/src/cannectivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,11 @@ int cannectivity_timestamp_init(void);
*/
int cannectivity_usb_init(void);

/**
* @brief CANnectivity USB DFU initialization function
*
* @return 0 on success, negative error number otherwise.
*/
int cannectivity_dfu_init(void);

#endif /* CANNECTIVITY_APP_CANNECTIVITY_H_ */
182 changes: 182 additions & 0 deletions app/src/dfu.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2024 Henrik Brix Andersen <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/sys_clock.h>
#include <zephyr/dfu/mcuboot.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/reboot.h>

#include "cannectivity.h"

LOG_MODULE_REGISTER(dfu, CONFIG_CANNECTIVITY_LOG_LEVEL);

/* DFU button poll timing */
#define DFU_BUTTON_POLL_HZ 5
#define DFU_BUTTON_POLL_INTERVAL_MS (MSEC_PER_SEC / DFU_BUTTON_POLL_HZ)
#define DFU_BUTTON_POLL_TOTAL (CONFIG_CANNECTIVITY_DFU_BUTTON_HOLD_TIME * DFU_BUTTON_POLL_HZ)

/* DFU button and LED devicetree nodes */
#define DFU_LED_NODE DT_ALIAS(mcuboot_led0)
#define DFU_BUTTON_NODE DT_ALIAS(mcuboot_button0)

#ifdef CONFIG_CANNECTIVITY_DFU_LED
struct gpio_dt_spec dfu_led = GPIO_DT_SPEC_GET(DFU_LED_NODE, gpios);
#endif /* CONFIG_CANNECTIVITY_DFU_LED */

#ifdef CONFIG_CANNECTIVITY_DFU_BUTTON
struct gpio_dt_spec dfu_button = GPIO_DT_SPEC_GET(DFU_BUTTON_NODE, gpios);
static struct gpio_callback dfu_button_cb;
static struct k_work_delayable dfu_button_work;
static struct k_sem dfu_button_sem;

static void dfu_button_poll(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
int err;

err = gpio_pin_get_dt(&dfu_button);
if (err < 0) {
LOG_ERR("failed to get DFU button state (err %d)", err);
goto done;
}

if (err > 0) {
#ifdef CONFIG_CANNECTIVITY_DFU_LED
err = gpio_pin_toggle_dt(&dfu_led);
if (err != 0) {
LOG_ERR("failed to toggle DFU LED (err %d)", err);
goto done;
}
#endif /* CONFIG_CANNECTIVITY_DFU_LED */

k_sem_give(&dfu_button_sem);
if (k_sem_count_get(&dfu_button_sem) >= DFU_BUTTON_POLL_TOTAL) {
LOG_INF("rebooting");
sys_reboot(SYS_REBOOT_COLD);
}

k_work_reschedule(dwork, K_MSEC(DFU_BUTTON_POLL_INTERVAL_MS));
return;
}

done:
#ifdef CONFIG_CANNECTIVITY_DFU_LED
err = gpio_pin_set_dt(&dfu_led, 0);
if (err != 0) {
LOG_ERR("failed to turn off DFU LED (err %d)", err);
return;
}
#endif /* CONFIG_CANNECTIVITY_DFU_LED */
}

static void dfu_button_interrupt(const struct device *port, struct gpio_callback *cb,
gpio_port_pins_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(cb);
ARG_UNUSED(pins);

k_sem_reset(&dfu_button_sem);
k_work_reschedule(&dfu_button_work, K_NO_WAIT);
}

static int dfu_button_init(void)
{
int err;

err = k_sem_init(&dfu_button_sem, 0, K_SEM_MAX_LIMIT);
if (err != 0) {
LOG_ERR("failed to initialize DFU button semaphore (err %d)", err);
return err;
}

if (!gpio_is_ready_dt(&dfu_button)) {
LOG_ERR("DFU button device not ready");
return -ENODEV;
}

err = gpio_pin_configure_dt(&dfu_button, GPIO_INPUT);
if (err != 0) {
LOG_ERR("failed to configure DFU button (err %d)", err);
return err;
}

err = gpio_pin_interrupt_configure_dt(&dfu_button, GPIO_INT_EDGE_TO_ACTIVE);
if (err != 0) {
LOG_ERR("failed to configure DFU button interrupt (err %d)", err);
return err;
}

k_work_init_delayable(&dfu_button_work, dfu_button_poll);

gpio_init_callback(&dfu_button_cb, dfu_button_interrupt, BIT(dfu_button.pin));
err = gpio_add_callback_dt(&dfu_button, &dfu_button_cb);
if (err != 0) {
LOG_ERR("failed to add DFU button callback (err %d)", err);
return err;
}

return 0;
}
#endif /* CONFIG_CANNECTIVITY_DFU_BUTTON */

#ifdef CONFIG_CANNECTIVITY_DFU_LED
static int dfu_led_init(void)
{
int err;

if (!gpio_is_ready_dt(&dfu_led)) {
LOG_ERR("DFU LED device not ready");
return -ENODEV;
}

err = gpio_pin_configure_dt(&dfu_led, GPIO_OUTPUT_INACTIVE);
if (err != 0) {
LOG_ERR("failed to turn off DFU LED (err %d)", err);
return err;
}

return 0;
}
#endif /* CONFIG_CANNECTIVITY_DFU_LED */

int cannectivity_dfu_init(void)
{
int err;

/*
* Confirm updated image if running under MCUboot booloader. This could be done on
* successful USB enumeration instead, but that could cause unwanted image reverts on
* e.g. self-powered development boards.
*/
if (!boot_is_img_confirmed()) {
err = boot_write_img_confirmed();
if (err != 0) {
LOG_ERR("failed to confirm image (err %d)", err);
return err;
}

LOG_INF("image confirmed");
}

#ifdef CONFIG_CANNECTIVITY_DFU_LED
err = dfu_led_init();
if (err != 0) {
return err;
}
#endif /* CONFIG_CANNECTIVITY_DFU_LED */

#ifdef CONFIG_CANNECTIVITY_DFU_BUTTON
err = dfu_button_init();
if (err != 0) {
return err;
}
#endif /* CONFIG_CANNECTIVITY_DFU_BUTTON */

return 0;
}
Loading