zephyr/subsys/usb/class/usb_dfu.c
Peter Bigot 611263921a usb: add optional work queue
The USB infrastructure currently uses the system work queue for
offloading transfers, CDC-ACM UART transmission/reception, and device
firmware activities.  This causes problems when the system work queue
is also used to initiate some activities (such as UART) that normally
complete without requiring an external thread: in that case the USB
infrastructure is prevented from making progress because the system
work queue is blocked waiting for the USB infrastructure to provide
data.

Break the dependency by allowing the USB infrastructure to use a
dedicated work queue which doesn't depend on availability of the
system work queue.

Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-09-16 13:32:22 -05:00

830 lines
21 KiB
C

/*******************************************************************************
*
* Copyright(c) 2015,2016 Intel Corporation.
* Copyright(c) 2017 PHYTEC Messtechnik GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************/
/**
* @brief DFU class driver
*
* USB DFU device class driver
*
*/
#include <init.h>
#include <kernel.h>
#include <stdio.h>
#include <errno.h>
#include <drivers/flash.h>
#include <storage/flash_map.h>
#include <dfu/mcuboot.h>
#include <dfu/flash_img.h>
#include <sys/byteorder.h>
#include <usb/usb_device.h>
#include <usb/usb_common.h>
#include <usb/class/usb_dfu.h>
#include <usb_descriptor.h>
#include <usb_work_q.h>
#define LOG_LEVEL CONFIG_USB_DEVICE_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(usb_dfu);
#define NUMOF_ALTERNATE_SETTINGS 2
#define USB_DFU_MAX_XFER_SIZE CONFIG_USB_REQUEST_BUFFER_SIZE
#define FIRMWARE_IMAGE_0_LABEL "image-0"
#define FIRMWARE_IMAGE_1_LABEL "image-1"
/* MCUBoot waits for CONFIG_USB_DFU_WAIT_DELAY_MS time in total to let DFU to
* be commenced. It intermittently checks every INTERMITTENT_CHECK_DELAY
* milliseconds to see if DFU has started.
*/
#define INTERMITTENT_CHECK_DELAY 50
static struct k_poll_event dfu_event;
static struct k_poll_signal dfu_signal;
static struct k_work dfu_work;
struct dfu_worker_data_t {
uint8_t buf[USB_DFU_MAX_XFER_SIZE];
enum dfu_state worker_state;
uint16_t worker_len;
};
static struct dfu_worker_data_t dfu_data_worker;
struct usb_dfu_config {
struct usb_if_descriptor if0;
struct dfu_runtime_descriptor dfu_descr;
} __packed;
USBD_CLASS_DESCR_DEFINE(primary, 0) struct usb_dfu_config dfu_cfg = {
/* Interface descriptor */
.if0 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_INTERFACE_DESC,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = DFU_DEVICE_CLASS,
.bInterfaceSubClass = DFU_SUBCLASS,
.bInterfaceProtocol = DFU_RT_PROTOCOL,
.iInterface = 0,
},
.dfu_descr = {
.bLength = sizeof(struct dfu_runtime_descriptor),
.bDescriptorType = DFU_FUNC_DESC,
.bmAttributes = DFU_ATTR_CAN_DNLOAD |
DFU_ATTR_CAN_UPLOAD |
DFU_ATTR_MANIFESTATION_TOLERANT,
.wDetachTimeOut =
sys_cpu_to_le16(CONFIG_USB_DFU_DETACH_TIMEOUT),
.wTransferSize =
sys_cpu_to_le16(USB_DFU_MAX_XFER_SIZE),
.bcdDFUVersion =
sys_cpu_to_le16(DFU_VERSION),
},
};
/* dfu mode device descriptor */
struct dev_dfu_mode_descriptor {
struct usb_device_descriptor device_descriptor;
struct usb_cfg_descriptor cfg_descr;
struct usb_sec_dfu_config {
struct usb_if_descriptor if0;
struct usb_if_descriptor if1;
struct dfu_runtime_descriptor dfu_descr;
} __packed sec_dfu_cfg;
} __packed;
USBD_DEVICE_DESCR_DEFINE(secondary)
struct dev_dfu_mode_descriptor dfu_mode_desc = {
/* Device descriptor */
.device_descriptor = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DEVICE_DESC,
.bcdUSB = sys_cpu_to_le16(USB_2_0),
.bDeviceClass = 0,
.bDeviceSubClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = USB_MAX_CTRL_MPS,
.idVendor = sys_cpu_to_le16((uint16_t)CONFIG_USB_DEVICE_VID),
.idProduct = sys_cpu_to_le16((uint16_t)CONFIG_USB_DEVICE_PID),
.bcdDevice = sys_cpu_to_le16(BCDDEVICE_RELNUM),
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1,
},
/* Configuration descriptor */
.cfg_descr = {
.bLength = sizeof(struct usb_cfg_descriptor),
.bDescriptorType = USB_CONFIGURATION_DESC,
.wTotalLength = 0,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_CONFIGURATION_ATTRIBUTES,
.bMaxPower = CONFIG_USB_MAX_POWER,
},
.sec_dfu_cfg = {
/* Interface descriptor */
.if0 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_INTERFACE_DESC,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 0,
.bInterfaceClass = DFU_DEVICE_CLASS,
.bInterfaceSubClass = DFU_SUBCLASS,
.bInterfaceProtocol = DFU_MODE_PROTOCOL,
.iInterface = 4,
},
.if1 = {
.bLength = sizeof(struct usb_if_descriptor),
.bDescriptorType = USB_INTERFACE_DESC,
.bInterfaceNumber = 0,
.bAlternateSetting = 1,
.bNumEndpoints = 0,
.bInterfaceClass = DFU_DEVICE_CLASS,
.bInterfaceSubClass = DFU_SUBCLASS,
.bInterfaceProtocol = DFU_MODE_PROTOCOL,
.iInterface = 5,
},
.dfu_descr = {
.bLength = sizeof(struct dfu_runtime_descriptor),
.bDescriptorType = DFU_FUNC_DESC,
.bmAttributes = DFU_ATTR_CAN_DNLOAD |
DFU_ATTR_CAN_UPLOAD |
DFU_ATTR_MANIFESTATION_TOLERANT,
.wDetachTimeOut =
sys_cpu_to_le16(CONFIG_USB_DFU_DETACH_TIMEOUT),
.wTransferSize =
sys_cpu_to_le16(USB_DFU_MAX_XFER_SIZE),
.bcdDFUVersion =
sys_cpu_to_le16(DFU_VERSION),
},
},
};
struct usb_string_desription {
struct usb_string_descriptor lang_descr;
struct usb_mfr_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bString[USB_BSTRING_LENGTH(
CONFIG_USB_DEVICE_MANUFACTURER)];
} __packed utf16le_mfr;
struct usb_product_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bString[USB_BSTRING_LENGTH(CONFIG_USB_DEVICE_PRODUCT)];
} __packed utf16le_product;
struct usb_sn_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bString[USB_BSTRING_LENGTH(CONFIG_USB_DEVICE_SN)];
} __packed utf16le_sn;
struct image_0_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bString[USB_BSTRING_LENGTH(FIRMWARE_IMAGE_0_LABEL)];
} __packed utf16le_image0;
struct image_1_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint8_t bString[USB_BSTRING_LENGTH(FIRMWARE_IMAGE_1_LABEL)];
} __packed utf16le_image1;
} __packed;
USBD_STRING_DESCR_DEFINE(secondary)
struct usb_string_desription string_descr = {
.lang_descr = {
.bLength = sizeof(struct usb_string_descriptor),
.bDescriptorType = USB_STRING_DESC,
.bString = sys_cpu_to_le16(0x0409),
},
/* Manufacturer String Descriptor */
.utf16le_mfr = {
.bLength = USB_STRING_DESCRIPTOR_LENGTH(
CONFIG_USB_DEVICE_MANUFACTURER),
.bDescriptorType = USB_STRING_DESC,
.bString = CONFIG_USB_DEVICE_MANUFACTURER,
},
/* Product String Descriptor */
.utf16le_product = {
.bLength = USB_STRING_DESCRIPTOR_LENGTH(
CONFIG_USB_DEVICE_PRODUCT),
.bDescriptorType = USB_STRING_DESC,
.bString = CONFIG_USB_DEVICE_PRODUCT,
},
/* Serial Number String Descriptor */
.utf16le_sn = {
.bLength = USB_STRING_DESCRIPTOR_LENGTH(CONFIG_USB_DEVICE_SN),
.bDescriptorType = USB_STRING_DESC,
.bString = CONFIG_USB_DEVICE_SN,
},
/* Image 0 String Descriptor */
.utf16le_image0 = {
.bLength = USB_STRING_DESCRIPTOR_LENGTH(
FIRMWARE_IMAGE_0_LABEL),
.bDescriptorType = USB_STRING_DESC,
.bString = FIRMWARE_IMAGE_0_LABEL,
},
/* Image 1 String Descriptor */
.utf16le_image1 = {
.bLength = USB_STRING_DESCRIPTOR_LENGTH(
FIRMWARE_IMAGE_1_LABEL),
.bDescriptorType = USB_STRING_DESC,
.bString = FIRMWARE_IMAGE_1_LABEL,
},
};
/* This element marks the end of the entire descriptor. */
USBD_TERM_DESCR_DEFINE(secondary) struct usb_desc_header term_descr = {
.bLength = 0,
.bDescriptorType = 0,
};
static struct usb_cfg_data dfu_config;
/* Device data structure */
struct dfu_data_t {
uint8_t flash_area_id;
uint32_t flash_upload_size;
/* Number of bytes sent during upload */
uint32_t bytes_sent;
uint32_t alt_setting; /* DFU alternate setting */
struct flash_img_context ctx;
enum dfu_state state; /* State of the DFU device */
enum dfu_status status; /* Status of the DFU device */
uint16_t block_nr; /* DFU block number */
uint16_t bwPollTimeout;
};
static struct dfu_data_t dfu_data = {
.state = appIDLE,
.status = statusOK,
.flash_area_id = FLASH_AREA_ID(image_1),
.alt_setting = 0,
.bwPollTimeout = CONFIG_USB_DFU_DEFAULT_POLLTIMEOUT,
};
/**
* @brief Helper function to check if in DFU app state.
*
* @return true if app state, false otherwise.
*/
static bool dfu_check_app_state(void)
{
if (dfu_data.state == appIDLE ||
dfu_data.state == appDETACH) {
dfu_data.state = appIDLE;
return true;
}
return false;
}
/**
* @brief Helper function to reset DFU internal counters.
*/
static void dfu_reset_counters(void)
{
dfu_data.bytes_sent = 0U;
dfu_data.block_nr = 0U;
if (flash_img_init(&dfu_data.ctx)) {
LOG_ERR("flash img init error");
dfu_data.state = dfuERROR;
dfu_data.status = errUNKNOWN;
}
}
static void dfu_flash_write(uint8_t *data, size_t len)
{
bool flush = false;
if (!len) {
/* Download completed */
flush = true;
}
if (flash_img_buffered_write(&dfu_data.ctx, data, len, flush)) {
LOG_ERR("flash write error");
dfu_data.state = dfuERROR;
dfu_data.status = errWRITE;
} else if (!len) {
LOG_DBG("flash write done");
dfu_data.state = dfuMANIFEST_SYNC;
dfu_reset_counters();
if (boot_request_upgrade(false)) {
dfu_data.state = dfuERROR;
dfu_data.status = errWRITE;
}
} else {
dfu_data.state = dfuDNLOAD_IDLE;
}
LOG_DBG("bytes written 0x%x", flash_img_bytes_written(&dfu_data.ctx));
}
/**
* @brief Handler called for DFU Class requests not handled by the USB stack.
*
* @param pSetup Information about the request to execute.
* @param len Size of the buffer.
* @param data Buffer containing the request result.
*
* @return 0 on success, negative errno code on fail.
*/
static int dfu_class_handle_req(struct usb_setup_packet *pSetup,
int32_t *data_len, uint8_t **data)
{
int ret;
uint32_t len, bytes_left;
switch (pSetup->bRequest) {
case DFU_GETSTATUS:
LOG_DBG("DFU_GETSTATUS: status %d, state %d",
dfu_data.status, dfu_data.state);
if (dfu_data.state == dfuMANIFEST_SYNC) {
dfu_data.state = dfuIDLE;
}
/* bStatus */
(*data)[0] = dfu_data.status;
/* bwPollTimeout */
sys_put_le16(dfu_data.bwPollTimeout, &(*data)[1]);
(*data)[3] = 0U;
/* bState */
(*data)[4] = dfu_data.state;
/* iString */
(*data)[5] = 0U;
*data_len = 6;
break;
case DFU_GETSTATE:
LOG_DBG("DFU_GETSTATE");
(*data)[0] = dfu_data.state;
*data_len = 1;
break;
case DFU_ABORT:
LOG_DBG("DFU_ABORT");
if (dfu_check_app_state()) {
return -EINVAL;
}
dfu_reset_counters();
dfu_data.state = dfuIDLE;
dfu_data.status = statusOK;
break;
case DFU_CLRSTATUS:
LOG_DBG("DFU_CLRSTATUS");
if (dfu_check_app_state()) {
return -EINVAL;
}
dfu_data.state = dfuIDLE;
dfu_data.status = statusOK;
break;
case DFU_DNLOAD:
LOG_DBG("DFU_DNLOAD block %d, len %d, state %d",
pSetup->wValue, pSetup->wLength, dfu_data.state);
if (dfu_check_app_state()) {
return -EINVAL;
}
switch (dfu_data.state) {
case dfuIDLE:
LOG_DBG("DFU_DNLOAD start");
dfu_reset_counters();
k_poll_signal_reset(&dfu_signal);
if (dfu_data.flash_area_id !=
FLASH_AREA_ID(image_1)) {
dfu_data.status = errWRITE;
dfu_data.state = dfuERROR;
LOG_ERR("This area can not be overwritten");
break;
}
dfu_data.state = dfuDNBUSY;
dfu_data_worker.worker_state = dfuIDLE;
dfu_data_worker.worker_len = pSetup->wLength;
memcpy(dfu_data_worker.buf, *data, pSetup->wLength);
k_work_submit_to_queue(&USB_WORK_Q, &dfu_work);
break;
case dfuDNLOAD_IDLE:
dfu_data.state = dfuDNBUSY;
dfu_data_worker.worker_state = dfuDNLOAD_IDLE;
dfu_data_worker.worker_len = pSetup->wLength;
if (dfu_data_worker.worker_len == 0U) {
dfu_data.state = dfuMANIFEST_SYNC;
k_poll_signal_raise(&dfu_signal, 0);
}
memcpy(dfu_data_worker.buf, *data, pSetup->wLength);
k_work_submit_to_queue(&USB_WORK_Q, &dfu_work);
break;
default:
LOG_ERR("DFU_DNLOAD wrong state %d", dfu_data.state);
dfu_data.state = dfuERROR;
dfu_data.status = errUNKNOWN;
dfu_reset_counters();
return -EINVAL;
}
break;
case DFU_UPLOAD:
LOG_DBG("DFU_UPLOAD block %d, len %d, state %d",
pSetup->wValue, pSetup->wLength, dfu_data.state);
if (dfu_check_app_state()) {
return -EINVAL;
}
switch (dfu_data.state) {
case dfuIDLE:
dfu_reset_counters();
LOG_DBG("DFU_UPLOAD start");
case dfuUPLOAD_IDLE:
if (!pSetup->wLength ||
dfu_data.block_nr != pSetup->wValue) {
LOG_DBG("DFU_UPLOAD block %d, expected %d, "
"len %d", pSetup->wValue,
dfu_data.block_nr, pSetup->wLength);
dfu_data.state = dfuERROR;
dfu_data.status = errUNKNOWN;
break;
}
/* Upload in progress */
bytes_left = dfu_data.flash_upload_size -
dfu_data.bytes_sent;
if (bytes_left < pSetup->wLength) {
len = bytes_left;
} else {
len = pSetup->wLength;
}
if (len > USB_DFU_MAX_XFER_SIZE) {
/*
* The host could requests more data as stated
* in wTransferSize. Limit upload length to the
* size of the request-buffer.
*/
len = USB_DFU_MAX_XFER_SIZE;
}
if (len) {
const struct flash_area *fa;
ret = flash_area_open(dfu_data.flash_area_id,
&fa);
if (ret) {
dfu_data.state = dfuERROR;
dfu_data.status = errFILE;
break;
}
ret = flash_area_read(fa, dfu_data.bytes_sent,
*data, len);
flash_area_close(fa);
if (ret) {
dfu_data.state = dfuERROR;
dfu_data.status = errFILE;
break;
}
}
*data_len = len;
dfu_data.bytes_sent += len;
dfu_data.block_nr++;
if (dfu_data.bytes_sent == dfu_data.flash_upload_size &&
len < pSetup->wLength) {
/* Upload completed when a
* short packet is received
*/
*data_len = 0;
dfu_data.state = dfuIDLE;
} else
dfu_data.state = dfuUPLOAD_IDLE;
break;
default:
LOG_ERR("DFU_UPLOAD wrong state %d", dfu_data.state);
dfu_data.state = dfuERROR;
dfu_data.status = errUNKNOWN;
dfu_reset_counters();
return -EINVAL;
}
break;
case DFU_DETACH:
LOG_DBG("DFU_DETACH timeout %d, state %d",
pSetup->wValue, dfu_data.state);
if (dfu_data.state != appIDLE) {
dfu_data.state = appIDLE;
return -EINVAL;
}
/* Move to appDETACH state */
dfu_data.state = appDETACH;
/* We should start a timer here but in order to
* keep things simple and do not increase the size
* we rely on the host to get us out of the appATTACHED
* state if needed.
*/
/* Set the DFU mode descriptors to be used after reset */
dfu_config.usb_device_description = (uint8_t *) &dfu_mode_desc;
if (usb_set_config(dfu_config.usb_device_description) != 0) {
LOG_ERR("usb_set_config failed in DFU_DETACH");
return -EIO;
}
break;
default:
LOG_WRN("DFU UNKNOWN STATE: %d", pSetup->bRequest);
return -EINVAL;
}
return 0;
}
/**
* @brief Callback used to know the USB connection status
*
* @param status USB device status code.
*
* @return N/A.
*/
static void dfu_status_cb(struct usb_cfg_data *cfg,
enum usb_dc_status_code status,
const uint8_t *param)
{
ARG_UNUSED(param);
ARG_UNUSED(cfg);
/* Check the USB status and do needed action if required */
switch (status) {
case USB_DC_ERROR:
LOG_DBG("USB device error");
break;
case USB_DC_RESET:
LOG_DBG("USB device reset detected, state %d", dfu_data.state);
if (dfu_data.state == appDETACH) {
dfu_data.state = dfuIDLE;
}
break;
case USB_DC_CONNECTED:
LOG_DBG("USB device connected");
break;
case USB_DC_CONFIGURED:
LOG_DBG("USB device configured");
break;
case USB_DC_DISCONNECTED:
LOG_DBG("USB device disconnected");
break;
case USB_DC_SUSPEND:
LOG_DBG("USB device supended");
break;
case USB_DC_RESUME:
LOG_DBG("USB device resumed");
break;
case USB_DC_SOF:
break;
case USB_DC_UNKNOWN:
default:
LOG_DBG("USB unknown state");
break;
}
}
/**
* @brief Custom handler for standard ('chapter 9') requests
* in order to catch the SET_INTERFACE request and
* extract the interface alternate setting
*
* @param pSetup Information about the request to execute.
* @param len Size of the buffer.
* @param data Buffer containing the request result.
*
* @return 0 if SET_INTERFACE request, -ENOTSUP otherwise.
*/
static int dfu_custom_handle_req(struct usb_setup_packet *pSetup,
int32_t *data_len, uint8_t **data)
{
ARG_UNUSED(data);
if (REQTYPE_GET_RECIP(pSetup->bmRequestType) ==
REQTYPE_RECIP_INTERFACE) {
if (pSetup->bRequest == REQ_SET_INTERFACE) {
LOG_DBG("DFU alternate setting %d", pSetup->wValue);
const struct flash_area *fa;
switch (pSetup->wValue) {
case 0:
dfu_data.flash_area_id =
FLASH_AREA_ID(image_0);
break;
case 1:
dfu_data.flash_area_id =
FLASH_AREA_ID(image_1);
break;
default:
LOG_WRN("Invalid DFU alternate setting");
return -ENOTSUP;
}
if (flash_area_open(dfu_data.flash_area_id, &fa)) {
return -EIO;
}
dfu_data.flash_upload_size = fa->fa_size;
flash_area_close(fa);
dfu_data.alt_setting = pSetup->wValue;
*data_len = 0;
return 0;
}
}
/* Not handled by us */
return -EINVAL;
}
static void dfu_interface_config(struct usb_desc_header *head,
uint8_t bInterfaceNumber)
{
ARG_UNUSED(head);
dfu_cfg.if0.bInterfaceNumber = bInterfaceNumber;
}
/* Configuration of the DFU Device send to the USB Driver */
USBD_CFG_DATA_DEFINE(primary, dfu) struct usb_cfg_data dfu_config = {
.usb_device_description = NULL,
.interface_config = dfu_interface_config,
.interface_descriptor = &dfu_cfg.if0,
.cb_usb_status = dfu_status_cb,
.interface = {
.class_handler = dfu_class_handle_req,
.custom_handler = dfu_custom_handle_req,
},
.num_endpoints = 0,
};
/*
* Dummy configuration, this is necessary to configure DFU mode descriptor
* which is an alternative (secondary) device descriptor.
*/
USBD_CFG_DATA_DEFINE(secondary, dfu) struct usb_cfg_data dfu_mode_config = {
.usb_device_description = NULL,
.interface_config = NULL,
.interface_descriptor = &dfu_mode_desc.sec_dfu_cfg.if0,
.cb_usb_status = dfu_status_cb,
.interface = {
.class_handler = dfu_class_handle_req,
.custom_handler = dfu_custom_handle_req,
},
.num_endpoints = 0,
};
static void dfu_work_handler(struct k_work *item)
{
ARG_UNUSED(item);
switch (dfu_data_worker.worker_state) {
case dfuIDLE:
/*
* If progressive erase is enabled, then erase take place while
* image collection, so not erase whole bank at DFU beginning
*/
#ifndef CONFIG_IMG_ERASE_PROGRESSIVELY
if (boot_erase_img_bank(FLASH_AREA_ID(image_1))) {
dfu_data.state = dfuERROR;
dfu_data.status = errERASE;
break;
}
#endif
case dfuDNLOAD_IDLE:
dfu_flash_write(dfu_data_worker.buf,
dfu_data_worker.worker_len);
break;
default:
LOG_ERR("OUT of state machine");
break;
}
}
static int usb_dfu_init(const struct device *dev)
{
const struct flash_area *fa;
ARG_UNUSED(dev);
k_work_init(&dfu_work, dfu_work_handler);
k_poll_signal_init(&dfu_signal);
if (flash_area_open(dfu_data.flash_area_id, &fa)) {
return -EIO;
}
dfu_data.flash_upload_size = fa->fa_size;
flash_area_close(fa);
return 0;
}
/**
* @brief Function to check if DFU is started.
*
* @return true if DNBUSY/DNLOAD_IDLE, false otherwise.
*/
static bool is_dfu_started(void)
{
if ((dfu_data.state == dfuDNBUSY) ||
(dfu_data.state == dfuDNLOAD_IDLE)) {
return true;
}
return false;
}
/**
* @brief Function to check and wait while the USB DFU is in progress.
*
* @return N/A
*/
void wait_for_usb_dfu(void)
{
/* Wait for a prescribed duration of time. If DFU hasn't started within
* that time, stop waiting and proceed further.
*/
for (int time = 0;
time < (CONFIG_USB_DFU_WAIT_DELAY_MS/INTERMITTENT_CHECK_DELAY);
time++) {
if (is_dfu_started()) {
k_poll_event_init(&dfu_event, K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY, &dfu_signal);
/* Wait till DFU is complete */
if (k_poll(&dfu_event, 1, K_FOREVER) != 0) {
LOG_DBG("USB DFU Error");
}
LOG_INF("USB DFU Completed");
break;
}
k_msleep(INTERMITTENT_CHECK_DELAY);
}
}
SYS_INIT(usb_dfu_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);