Skip to content

usb: device_next: implement USB DFU class for the new device support #79794

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

Merged
merged 4 commits into from
Feb 4, 2025
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
1 change: 1 addition & 0 deletions doc/connectivity/usb/device_next/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ New USB device support APIs
uac2_device.rst
usbd_msc_device.rst
usb_midi.rst
usbd_dfu.rst
11 changes: 11 additions & 0 deletions doc/connectivity/usb/device_next/api/usbd_dfu.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. _usbd_dfu:

USB DFU device API
##################

USB DFU device specific API defined in :zephyr_file:`include/zephyr/usb/class/usbd_dfu.h`.

API Reference
*************

.. doxygengroup:: usbd_dfu
170 changes: 170 additions & 0 deletions include/zephyr/usb/class/usbd_dfu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @file
* @brief USB Device Firmware Upgrade (DFU) public header
*
* Header exposes API for registering DFU images.
*/

#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_DFU_H
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_DFU_H

#include <stdint.h>

/* DFU Class Subclass */
#define USB_DFU_SUBCLASS 0x01

/* DFU Class runtime Protocol */
#define USB_DFU_PROTOCOL_RUNTIME 0x01

/* DFU Class DFU mode Protocol */
#define USB_DFU_PROTOCOL_DFU 0x02

/* DFU Class Specific Requests */
#define USB_DFU_REQ_DETACH 0x00
#define USB_DFU_REQ_DNLOAD 0x01
#define USB_DFU_REQ_UPLOAD 0x02
#define USB_DFU_REQ_GETSTATUS 0x03
#define USB_DFU_REQ_CLRSTATUS 0x04
#define USB_DFU_REQ_GETSTATE 0x05
#define USB_DFU_REQ_ABORT 0x06

/* Run-Time DFU Functional Descriptor */
struct usb_dfu_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bmAttributes;
uint16_t wDetachTimeOut;
uint16_t wTransferSize;
uint16_t bcdDFUVersion;
} __packed;

/* DFU Functional Descriptor Type */
#define USB_DESC_DFU_FUNCTIONAL 0x21

/* DFU attributes DFU Functional Descriptor */
#define USB_DFU_ATTR_WILL_DETACH BIT(3)
#define USB_DFU_ATTR_MANIFESTATION_TOLERANT BIT(2)
#define USB_DFU_ATTR_CAN_UPLOAD BIT(1)
#define USB_DFU_ATTR_CAN_DNLOAD BIT(0)

/* DFU Specification release */
#define USB_DFU_VERSION 0x0110

/* DFU device status */
enum usb_dfu_status {
ERR_OK = 0x00,
ERR_TARGET = 0x01,
ERR_FILE = 0x02,
ERR_WRITE = 0x03,
ERR_ERASE = 0x04,
ERR_CHECK_ERASED = 0x05,
ERR_PROG = 0x06,
ERR_VERIFY = 0x07,
ERR_ADDRESS = 0x08,
ERR_NOTDONE = 0x09,
ERR_FIRMWARE = 0x0A,
ERR_VENDOR = 0x0B,
ERR_USBR = 0x0C,
ERR_POR = 0x0D,
ERR_UNKNOWN = 0x0E,
ERR_STALLEDPKT = 0x0F,
};

/* DFU device states */
enum usb_dfu_state {
APP_IDLE = 0,
APP_DETACH = 1,
DFU_IDLE = 2,
DFU_DNLOAD_SYNC = 3,
DFU_DNBUSY = 4,
DFU_DNLOAD_IDLE = 5,
DFU_MANIFEST_SYNC = 6,
DFU_MANIFEST = 7,
DFU_MANIFEST_WAIT_RST = 8,
DFU_UPLOAD_IDLE = 9,
DFU_ERROR = 10,
DFU_STATE_MAX = 11,
};

struct usbd_dfu_image {
const char *name;
struct usb_if_descriptor *const if_desc;
void *const priv;
struct usbd_desc_node *const sd_nd;
bool (*next_cb)(void *const priv,
const enum usb_dfu_state state, const enum usb_dfu_state next);
int (*read_cb)(void *const priv,
const uint32_t block, const uint16_t size,
uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE]);
int (*write_cb)(void *const priv,
const uint32_t block, const uint16_t size,
const uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE]);
};

/**
* @brief USB DFU device update API
* @defgroup usbd_dfu USB DFU device update API
* @ingroup usb
* @{
*/

/**
* @brief Define USB DFU image
*
* Use this macro to create USB DFU image
*
* The callbacks must be in form:
*
* @code{.c}
* static int read(void *const priv, const uint32_t block, const uint16_t size,
* uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])
* {
* int len;
*
* return len;
* }
*
* static int write(void *const priv, const uint32_t block, const uint16_t size,
* const uint8_t buf[static CONFIG_USBD_DFU_TRANSFER_SIZE])
* {
* return 0;
* }
*
* static bool next(void *const priv,
* const enum usb_dfu_state state, const enum usb_dfu_state next)
* {
* return true;
* }
* @endcode
*
* @param id Identifier by which the linker sorts registered images
* @param iname Image name as used in interface descriptor
* @param iread Image read callback
* @param iwrite Image write callback
* @param inext Notify/confirm next state
*/
#define USBD_DFU_DEFINE_IMG(id, iname, ipriv, iread, iwrite, inext) \
static __noinit struct usb_if_descriptor usbd_dfu_iface_##id; \
\
USBD_DESC_STRING_DEFINE(usbd_dfu_str_##id, iname, USBD_DUT_STRING_INTERFACE); \
\
static const STRUCT_SECTION_ITERABLE(usbd_dfu_image, usbd_dfu_image_##id) = { \
.name = iname, \
.if_desc = &usbd_dfu_iface_##id, \
.priv = ipriv, \
.sd_nd = &usbd_dfu_str_##id, \
.read_cb = iread, \
.write_cb = iwrite, \
.next_cb = inext, \
}

/**
* @}
*/
#endif /* ZEPHYR_INCLUDE_USB_CLASS_USBD_DFU_H */
18 changes: 17 additions & 1 deletion include/zephyr/usb/usbd.h
Original file line number Diff line number Diff line change
Expand Up @@ -841,14 +841,30 @@ int usbd_register_class(struct usbd_context *uds_ctx,
* usbd_register_class for any device, configuration number, or instance,
* either usbd_register_class or this function will fail.
*
* There may be situations where a particular function should not be
* registered, for example, when using the USB DFU implementation, the DFU mode
* function must be excluded during normal device operation. To do this, the
* device can pass a blocklist in the form shown below as an optional argument.
* If the blocklist is not needed, the argument should be NULL.
*
* @code{.c}
* static const char *const blocklist[] = {
* "dfu_dfu",
* NULL,
* };
* @endcode
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] speed Configuration speed
* @param[in] cfg Configuration value (bConfigurationValue)
* @param[in] blocklist Null pointer terminated array of pointers to string
* literals to be used as a block list
*
* @return 0 on success, other values on fail.
*/
int usbd_register_all_classes(struct usbd_context *uds_ctx,
const enum usbd_speed speed, uint8_t cfg);
const enum usbd_speed speed, uint8_t cfg,
const char *const blocklist[]);

/**
* @brief Unregister an USB class instance
Expand Down
6 changes: 6 additions & 0 deletions include/zephyr/usb/usbd_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ enum usbd_msg_type {
USBD_MSG_CDC_ACM_LINE_CODING,
/** CDC ACM Line State update */
USBD_MSG_CDC_ACM_CONTROL_LINE_STATE,
/** USB DFU class detach request */
USBD_MSG_DFU_APP_DETACH,
/** USB DFU class download completed */
USBD_MSG_DFU_DOWNLOAD_COMPLETED,
/** Maximum number of message types */
USBD_MSG_MAX_NUMBER,
};
Expand All @@ -70,6 +74,8 @@ static const char *const usbd_msg_type_list[] = {
"Stack error",
"CDC ACM line coding",
"CDC ACM control line state",
"DFU detach request",
"DFU download completed",
};

BUILD_ASSERT(ARRAY_SIZE(usbd_msg_type_list) == USBD_MSG_MAX_NUMBER,
Expand Down
11 changes: 9 additions & 2 deletions samples/subsys/usb/common/sample_usbd_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ LOG_MODULE_REGISTER(usbd_sample_config);

#define ZEPHYR_PROJECT_USB_VID 0x2fe3

/* By default, do not register the USB DFU class DFU mode instance. */
static const char *const blocklist[] = {
"dfu_dfu",
NULL,
};

/* doc device instantiation start */
/*
* Instantiate a context named sample_usbd using the default USB device
Expand Down Expand Up @@ -124,7 +130,8 @@ struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb)
return NULL;
}

err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_HS, 1);
err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_HS, 1,
blocklist);
if (err) {
LOG_ERR("Failed to add register classes");
return NULL;
Expand All @@ -143,7 +150,7 @@ struct usbd_context *sample_usbd_setup_device(usbd_msg_cb_t msg_cb)
/* doc configuration register end */

/* doc functions register start */
err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_FS, 1);
err = usbd_register_all_classes(&sample_usbd, USBD_SPEED_FS, 1, blocklist);
if (err) {
LOG_ERR("Failed to add register classes");
return NULL;
Expand Down
9 changes: 9 additions & 0 deletions samples/subsys/usb/dfu-next/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(usb-dfu)

include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
36 changes: 36 additions & 0 deletions samples/subsys/usb/dfu-next/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

menu "USB DFU sample options"

config APP_USB_DFU_USE_FLASH_BACKEND
select FLASH
select FLASH_MAP
select STREAM_FLASH
select IMG_MANAGER
select IMG_ERASE_PROGRESSIVELY
bool "Option to clear the flash area before mounting"
help
Use this to force an existing file system to be created.

if APP_USB_DFU_USE_FLASH_BACKEND

config USBD_DFU_FLASH
default y

config USBD_DFU_FLASH_SLOT0
default n

config USBD_DFU_FLASH_SLOT1
default y

endif

endmenu

# Source common USB sample options used to initialize new experimental USB
# device stack. The scope of these options is limited to USB samples in project
# tree, you cannot use them in your own application.
source "samples/subsys/usb/common/Kconfig.sample_usbd"

source "Kconfig.zephyr"
Loading
Loading