845 lines
21 KiB
C
845 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;
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
struct usb_if_descriptor if1;
|
|
#endif
|
|
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,
|
|
},
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
.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,
|
|
},
|
|
#endif
|
|
.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;
|
|
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
struct image_1_descriptor {
|
|
uint8_t bLength;
|
|
uint8_t bDescriptorType;
|
|
uint8_t bString[USB_BSTRING_LENGTH(FIRMWARE_IMAGE_1_LABEL)];
|
|
} __packed utf16le_image1;
|
|
#endif
|
|
} __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,
|
|
},
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
/* Image 1 String Descriptor */
|
|
.utf16le_image1 = {
|
|
.bLength = USB_STRING_DESCRIPTOR_LENGTH(
|
|
FIRMWARE_IMAGE_1_LABEL),
|
|
.bDescriptorType = USB_STRING_DESC,
|
|
.bString = FIRMWARE_IMAGE_1_LABEL,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
/* 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;
|
|
};
|
|
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
#define UPLOAD_FLASH_AREA_ID FLASH_AREA_ID(image_1)
|
|
#else
|
|
#define UPLOAD_FLASH_AREA_ID FLASH_AREA_ID(image_0)
|
|
#endif
|
|
|
|
|
|
static struct dfu_data_t dfu_data = {
|
|
.state = appIDLE,
|
|
.status = statusOK,
|
|
.flash_area_id = UPLOAD_FLASH_AREA_ID,
|
|
.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;
|
|
}
|
|
|
|
k_poll_signal_raise(&dfu_signal, 0);
|
|
} 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 !=
|
|
UPLOAD_FLASH_AREA_ID) {
|
|
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;
|
|
|
|
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;
|
|
#if FLASH_AREA_LABEL_EXISTS(image_1)
|
|
case 1:
|
|
dfu_data.flash_area_id =
|
|
UPLOAD_FLASH_AREA_ID;
|
|
break;
|
|
#endif
|
|
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(UPLOAD_FLASH_AREA_ID)) {
|
|
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);
|