1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

usbus/dfu: introduce initial Device Firmware Upgrade support for USBUS

This commit is contained in:
dylad 2020-12-15 17:11:31 +01:00 committed by Dylan Laduranty
parent ca34879a91
commit 669a8ec7b3
7 changed files with 502 additions and 0 deletions

View File

@ -976,6 +976,12 @@ ifneq (,$(filter usbus_hid,$(USEMODULE)))
USEMODULE += usbus
endif
ifneq (,$(filter usbus_dfu,$(USEMODULE)))
FEATURES_REQUIRED += riotboot
USEMODULE += usbus
USEMODULE += riotboot_slot
endif
ifneq (,$(filter uuid,$(USEMODULE)))
USEMODULE += hashes
USEMODULE += random

View File

@ -130,3 +130,8 @@ endif
ifneq (,$(filter prng,$(USEMODULE)))
include $(RIOTBASE)/sys/random/Makefile.include
endif
ifneq (,$(filter usbus_dfu,$(USEMODULE)))
CFLAGS += -DCPU_RAM_BASE=$(RAM_START_ADDR)
CFLAGS += -DCPU_RAM_SIZE=$(RAM_LEN)
endif

136
sys/include/usb/dfu.h Normal file
View File

@ -0,0 +1,136 @@
/*
* Copyright (C) 2020 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @defgroup usb_dfu DFU - USB Device Firmware Upgrade
* @ingroup usb
* @brief Generic USB DFU defines and helpers
*
* @{
*
* @file
* @brief Definition for USB DFU interfaces
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*/
#ifndef USB_DFU_H
#define USB_DFU_H
#include <stdint.h>
#include "usb.h"
#include "usb/descriptor.h"
#ifdef __cplusplus
extern "C" {
#endif
#define USB_IF_DESCRIPTOR_DFU 0x21 /**< USB DFU type descriptor*/
#define USB_DFU_VERSION_BCD 0x0110 /**< USB DFU version in BCD */
/**
* @name Default USB detach timeout for DFU descriptor
* @{
*/
#ifndef USB_DFU_DETACH_TIMEOUT_MS
#define USB_DFU_DETACH_TIMEOUT_MS 255
#endif
/** @} */
/**
* @name USB DFU interface attributes
* @{
*/
#define USB_DFU_CAN_DOWNLOAD 0x01 /**< DFU Download attribute */
#define USB_DFU_CAN_UPLOAD 0x02 /**< DFU Upload attribute */
#define USB_DFU_MANIFEST_TOLERANT 0x04 /**< DFU Manifest tolerant attribute */
#define USB_DFU_WILL_DETACH 0x08 /**< DFU Detach capabability attribute */
/** @} */
/**
* @name USB DFU interface type
* @{
*/
#define USB_DFU_INTERFACE 0xFE /** Application Specific Interface */
/** @} */
/**
* @name USB DFU subclass types
* @anchor usb_dfu_subtype
* @{
*/
#define USB_DFU_SUBCLASS_DFU 0x01 /**< DFU subclass */
/** @} */
/**
* @name USB DFU protocol types
* @{
*/
#define USB_DFU_PROTOCOL_RUNTIME_MODE 0x01 /**< Runtime mode */
#define USB_DFU_PROTOCOL_DFU_MODE 0x02 /**< DFU mode */
/** @} */
/**
* @name USB DFU setup request
* @{
*/
#define DFU_DETACH 0x00 /**< DFU Detach request */
#define DFU_DOWNLOAD 0x01 /**< DFU Download request */
#define DFU_UPLOAD 0x02 /**< DFU Upload request */
#define DFU_GET_STATUS 0x03 /**< DFU Get Status request */
#define DFU_CLR_STATUS 0x04 /**< DFU Clear Status request */
#define DFU_GET_STATE 0x05 /**< DFU Get State request */
#define DFU_ABORT 0x06 /**< DFU Abort request */
/** @} */
/**
* @brief USBUS DFU internal state
*/
typedef enum {
USB_DFU_STATE_APP_IDLE, /**< DFU application idle */
USB_DFU_STATE_APP_DETACH, /**< DFU application detach (reboot to DFU mode) */
USB_DFU_STATE_DFU_IDLE, /**< DFU runtime mode idle */
USB_DFU_STATE_DFU_DL_SYNC, /**< DFU download synchronization */
USB_DFU_STATE_DFU_DL_BUSY, /**< DFU download busy */
USB_DFU_STATE_DFU_DL_IDLE, /**< DFU download idle */
USB_DFU_STATE_DFU_MANIFEST_SYNC, /**< DFU manifest synchronization */
USB_DFU_STATE_DFU_MANIFEST, /**< DFU manifest mode */
USB_DFU_STATE_DFU_MANIFEST_WAIT_RST, /**< DFU manifest wait for CPU reset */
USB_DFU_STATE_DFU_UP_IDLE, /**< DFU upload idle */
USB_DFU_STATE_DFU_ERROR /**< DFU internal error */
} usb_dfu_state_t;
/**
* @brief USB DFU interface descriptor
*/
typedef struct __attribute__((packed)) {
uint8_t length; /**< Descriptor length */
uint8_t type; /**< Descriptor type */
uint8_t attribute; /**< Descriptor attributes flags */
uint16_t detach_timeout; /**< Descriptor detach timeout (ms) */
uint16_t xfer_size; /**< Descriptor transaction size */
uint16_t bcd_dfu; /**< Descriptor bcd version */
} usb_desc_if_dfu_t;
/**
* @brief USB DFU get_status control request packet
*/
typedef struct __attribute__((packed)) {
uint8_t status; /**< DFU status response */
uint32_t timeout : 24; /**< DFU timeout (ms) response */
uint8_t state; /**< DFU internal state machine */
uint8_t string; /**< DFU string */
} dfu_get_status_pkt_t;
#ifdef __cplusplus
}
#endif
#endif /* USB_DFU_H */
/** @} */

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2020 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @defgroup usbus_dfu USBUS DFU - USB Device Firmware Upgrade
* @ingroup usb
* @brief Specific USB DFU defines and helpers for USBUS
*
* @{
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
*/
#ifndef USB_USBUS_DFU_H
#define USB_USBUS_DFU_H
#include "usb/dfu.h"
#include "riotboot/flashwrite.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USBUS DFU device interface context
*/
typedef struct usbus_dfu_device {
usbus_handler_t handler_ctrl; /**< Control interface handler */
usbus_interface_t iface; /**< Control interface */
usbus_descr_gen_t dfu_descr; /**< DFU descriptor generator */
usbus_string_t slot0_str; /**< Descriptor string for Slot 0 */
#ifdef MODULE_RIOTBOOT_USB_DFU
usbus_interface_alt_t iface_alt_slot1; /**< Alt interface for secondary slot */
usbus_string_t slot1_str; /**< Descriptor string for Slot 1 */
riotboot_flashwrite_t writer; /**< DFU firmware update state structure */
#endif
bool skip_signature; /**< Skip RIOTBOOT signature status */
usbus_t *usbus; /**< Ptr to the USBUS context */
unsigned mode; /**< 0 - APP mode, 1 DFU mode */
unsigned selected_slot; /**< Slot used for upgrade */
usb_dfu_state_t dfu_state; /**< Internal DFU state machine */
} usbus_dfu_device_t;
/**
* @brief DFU initialization function
*
* @param usbus USBUS thread to use
* @param handler DFU device struct
* @param mode DFU start mode (0 runtime mode / 1 dfu mode)
*/
void usbus_dfu_init(usbus_t *usbus, usbus_dfu_device_t *handler, unsigned mode);
#ifdef __cplusplus
}
#endif
#endif /* USB_USBUS_DFU_H */
/** @} */

View File

@ -7,6 +7,9 @@ endif
ifneq (,$(filter usbus_cdc_acm,$(USEMODULE)))
DIRS += cdc/acm
endif
ifneq (,$(filter usbus_dfu,$(USEMODULE)))
DIRS += dfu/
endif
ifneq (,$(filter usbus_hid,$(USEMODULE)))
DIRS += hid
endif

View File

@ -0,0 +1,4 @@
MODULE = usbus_dfu
SRC = dfu.c
include $(RIOTBASE)/Makefile.base

285
sys/usb/usbus/dfu/dfu.c Normal file
View File

@ -0,0 +1,285 @@
/*
* Copyright (C) 2020 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup usbus_dfu
* @{
* @file USBUS implementation for device firmware upgrade
*
*
* @author Dylan Laduranty <dylan.laduranty@mesotic.com>
* @}
*/
#define USB_H_USER_IS_RIOT_INTERNAL
#include "usb/dfu.h"
#include "usb/descriptor.h"
#include "usb/usbus.h"
#include "usb/usbus/control.h"
#include "usb/usbus/dfu.h"
#include "riotboot/usb_dfu.h"
#ifdef MODULE_RIOTBOOT_USB_DFU
#include "xtimer.h"
#endif
#include "periph/pm.h"
#include "riotboot/slot.h"
#include <string.h>
#define ENABLE_DEBUG 0
#include "debug.h"
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event);
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup);
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event);
static void _init(usbus_t *usbus, usbus_handler_t *handler);
#ifdef MODULE_RIOTBOOT_USB_DFU
static void _reboot(void *arg);
static xtimer_t scheduled_reboot = { .callback=_reboot };
#define REBOOT_DELAY 2
#endif
#define DEFAULT_XFER_SIZE 64
static size_t _gen_dfu_descriptor(usbus_t *usbus, void *arg)
{
(void)arg;
usb_desc_if_dfu_t if_desc;
/* functional dfu descriptor */
if_desc.length = sizeof(usb_desc_if_dfu_t);
if_desc.type = USB_IF_DESCRIPTOR_DFU;
if_desc.attribute = USB_DFU_WILL_DETACH | USB_DFU_CAN_DOWNLOAD;
if_desc.detach_timeout = USB_DFU_DETACH_TIMEOUT_MS;
if_desc.xfer_size = DEFAULT_XFER_SIZE;
if_desc.bcd_dfu = USB_DFU_VERSION_BCD;
usbus_control_slicer_put_bytes(usbus, (uint8_t *)&if_desc, sizeof(if_desc));
return sizeof(usb_desc_if_dfu_t);
}
static const usbus_handler_driver_t dfu_driver = {
.init = _init,
.event_handler = _event_handler,
.transfer_handler = _transfer_handler,
.control_handler = _control_handler,
};
/* Descriptors */
static const usbus_descr_gen_funcs_t _dfu_descriptor = {
.fmt_post_descriptor = _gen_dfu_descriptor,
.fmt_pre_descriptor = NULL,
.len = {
.fixed_len = sizeof(usb_desc_if_dfu_t),
},
.len_type = USBUS_DESCR_LEN_FIXED,
};
#ifdef MODULE_RIOTBOOT_USB_DFU
static void _reboot(void *arg)
{
(void)arg;
pm_reboot();
}
#endif
void usbus_dfu_init(usbus_t *usbus, usbus_dfu_device_t *handler, unsigned mode)
{
DEBUG("DFU: initialization\n");
assert(usbus);
assert(handler);
assert((SLOT0_OFFSET % FLASHPAGE_SIZE) == 0);
memset(handler, 0, sizeof(usbus_dfu_device_t));
handler->usbus = usbus;
handler->handler_ctrl.driver = &dfu_driver;
handler->mode = mode;
handler->selected_slot = UINT32_MAX;
handler->skip_signature = true;
handler->dfu_state = (handler->mode == USB_DFU_PROTOCOL_DFU_MODE) ?
USB_DFU_STATE_DFU_IDLE : USB_DFU_STATE_APP_IDLE;
usbus_register_event_handler(usbus, (usbus_handler_t *)handler);
}
static void _init(usbus_t *usbus, usbus_handler_t *handler)
{
usbus_dfu_device_t *dfu = (usbus_dfu_device_t*) handler;
/* Set up descriptor generators */
dfu->dfu_descr.next = NULL;
dfu->dfu_descr.funcs = &_dfu_descriptor;
dfu->dfu_descr.arg = dfu;
/* Configure Interface 0 as control interface */
dfu->iface.class = USB_DFU_INTERFACE;
dfu->iface.subclass = USB_DFU_SUBCLASS_DFU;
dfu->iface.protocol = dfu->mode;
dfu->iface.descr_gen = &dfu->dfu_descr;
dfu->iface.handler = handler;
/* Create needed string descriptor for the interface and its alternate settings */
if (IS_ACTIVE(MODULE_RIOTBOOT_USB_DFU)) {
usbus_add_string_descriptor(usbus, &dfu->slot0_str, USB_DFU_MODE_SLOT0_NAME);
}
else {
usbus_add_string_descriptor(usbus, &dfu->slot0_str, USB_APP_MODE_SLOT_NAME);
}
/* Add string descriptor to the interface */
dfu->iface.descr = &dfu->slot0_str;
#ifdef MODULE_RIOTBOOT_USB_DFU
/* Create needed string descriptor for the alternate settings */
usbus_add_string_descriptor(usbus, &dfu->slot1_str, USB_DFU_MODE_SLOT1_NAME);
/* Add string descriptor to the alternate settings */
dfu->iface_alt_slot1.descr = &dfu->slot1_str;
/* attached alternate settings to their interface */
usbus_add_interface_alt(&dfu->iface, &dfu->iface_alt_slot1);
/* Start xtimer for scheduled reboot after firmware upgrade */
xtimer_init();
#endif
/* Add interface to the stack */
usbus_add_interface(usbus, &dfu->iface);
usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET);
}
static void _dfu_class_control_req(usbus_t *usbus, usbus_dfu_device_t *dfu, usb_setup_t *pkt)
{
static const usbopt_enable_t disable = USBOPT_DISABLE;
DEBUG("DFU control request:%x\n", pkt->request);
switch (pkt->request) {
case DFU_DETACH:
/* Detach USB bus */
usbdev_set(usbus->dev, USBOPT_ATTACH, &disable, sizeof(usbopt_enable_t));
/* Restart and jump into the bootloader */
uint32_t *reset_addr = (uint32_t *)RIOTBOOT_DFU_ADDR;
*reset_addr = RIOTBOOT_MAGIC_NUMBER;
pm_reboot();
break;
#ifdef MODULE_RIOTBOOT_USB_DFU
case DFU_DOWNLOAD:
/* Host indicates end of firmware download */
if (pkt->length == 0) {
/* Set DFU to manifest sync */
dfu->dfu_state = USB_DFU_STATE_DFU_MANIFEST_SYNC;
riotboot_flashwrite_flush(&dfu->writer);
riotboot_flashwrite_finish(&dfu->writer);
}
else if (dfu->dfu_state != USB_DFU_STATE_DFU_DL_SYNC) {
dfu->dfu_state = USB_DFU_STATE_DFU_DL_SYNC;
}
else {
/* Retrieve firmware data */
size_t len = 0;
uint8_t *data = usbus_control_get_out_data(usbus, &len);
/* skip writing the riotboot signature */
if (dfu->skip_signature) {
riotboot_flashwrite_init(&dfu->writer, dfu->selected_slot);
len -= RIOTBOOT_FLASHWRITE_SKIPLEN;
dfu->skip_signature = false;
riotboot_flashwrite_putbytes(&dfu->writer,
&data[RIOTBOOT_FLASHWRITE_SKIPLEN],
len, true);
}
else {
riotboot_flashwrite_putbytes(&dfu->writer, data, len, true);
}
}
break;
#endif
case DFU_GET_STATUS:
{
dfu_get_status_pkt_t buf;
if (dfu->dfu_state == USB_DFU_STATE_DFU_DL_SYNC) {
dfu->dfu_state = USB_DFU_STATE_DFU_DL_IDLE;
DEBUG("GET STATUS GO TO IDLE\n");
}
else if (dfu->dfu_state == USB_DFU_STATE_DFU_MANIFEST_SYNC) {
/* Scheduled reboot, so we can answer back dfu-util before rebooting */
dfu->dfu_state = USB_DFU_STATE_DFU_DL_IDLE;
#ifdef MODULE_RIOTBOOT_USB_DFU
xtimer_set(&scheduled_reboot, 1 * US_PER_SEC);
#endif
}
memset(&buf, 0, sizeof(buf));
buf.status = 0;
buf.timeout = USB_DFU_DETACH_TIMEOUT_MS;
buf.state = dfu->dfu_state;
/* Send answer to host */
usbus_control_slicer_put_bytes(usbus, (uint8_t*)&buf, sizeof(buf));
DEBUG("send answer\n");
break;
}
case DFU_CLR_STATUS:
DEBUG("CLRSTATUS To be implemented\n");
break;
default:
DEBUG("Unhandled DFU control request:%d\n", pkt->request);
}
}
static int _control_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_control_request_state_t state,
usb_setup_t *setup)
{
(void)usbus;
(void)state;
usbus_dfu_device_t *dfu = (usbus_dfu_device_t *)handler;
DEBUG("DFU: Request: 0x%x\n", setup->request);
/* Process DFU class request */
if (setup->type & USB_SETUP_REQUEST_TYPE_CLASS) {
_dfu_class_control_req(usbus, dfu, setup);
}
else {
switch (setup->request) {
case USB_SETUP_REQ_SET_INTERFACE:
DEBUG("DFU: Select alt interface %d\n", setup->value);
dfu->selected_slot = (unsigned)setup->value;
break;
default:
return -1;
}
}
return 1;
}
static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler,
usbdev_ep_t *ep, usbus_event_transfer_t event)
{
(void)event;
(void)usbus;
(void)handler;
(void)ep;
}
static void _event_handler(usbus_t *usbus, usbus_handler_t *handler,
usbus_event_usb_t event)
{
(void) usbus;
(void) handler;
switch (event) {
default:
DEBUG("Unhandled event :0x%x\n", event);
break;
}
}