Skip to content

Commit

Permalink
[USB] Support DFU runtime protocol along CDC
Browse files Browse the repository at this point in the history
This adds support for the DFU runtime protocol, which allows resetting
into the bootloader using a DFU command. This allows e.g. dfu-util to
handle the complete firmware upload, including the needed reset.

This consists of a number of changes:
 - An extra interface is added to the USB configuration descriptor. This
   descriptor has two parts (interface descriptor and functional
   descriptor) which together indicate to a host that this device
   supports DFU.
 - Control packets to this new interface are detected by the CDC code an
   forwarded to a new USBD_DFU_Runtime_Control() function.
 - This new function handles the DFU GET_STATE, GET_STATUS and
   DFU_DETACH commands. The former are optional, but simple enough, the
   latter is mandatory and handles resetting into the bootloader.
 - The CDC device descriptor is changed to become a composite device
   (CDC and DFU). This allows operating systems (in particular Windows,
   Linux did not really need this) to identify two different subdevices,
   and install different drivers for each (on Windows, this is serusb
   for the CDC part and WinUSB/libusb for the DFU part). Without this,
   dfu-util on Windows could not access the DFU commands when the serial
   driver was loaded.

   Because the CDC functionality already exposes two interfaces (which
   together form a single serial port), an IAD (Interface Association
   Descriptor) is inserted before these interfaces to group them
   together in a single subdevice. No IAD is needed for the DFU
   interface, since it is just a single interface.

   To become a composite device, the device class must be changed from
   CDC to a composite device class. This was originally class 0/0/0, but
   together with the IAD, a new EF/2/1 deviceclass was also introduced,
   which is used now.

Note that this only adds descriptors and a command handler on the
default control endpoint, so no extra (scarce) endpoints are used by
this, just a bit of memory.

This commit is still a bit rough, because:
 - The DFU descriptors and code are now pulled in directly by the CDC
   code (and HID is not supported yet). Ideally, there should be some
   kind of pluggable USB library where different interfaces can be
   registered independent of each other (see also
   #687).
 - The interface number is hardcoded in the DFU descriptor.
 - The reset to bootloader happens immediately, while it might be better
   to wait a short while to allow the current USB transaction to
   complete.
 - DFU support is unconditionally advertised, while not all boards might
   support DFU.
  • Loading branch information
matthijskooijman authored and fpistm committed Sep 18, 2020
1 parent 59e0103 commit 1173c3d
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 12 deletions.
81 changes: 74 additions & 7 deletions cores/arduino/stm32/usb/cdc/usbd_cdc.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,26 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
0x00,
0x02, /* bNumInterfaces: 2 interface */
0x03, /* bNumInterfaces: 3 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: self powered */
0x32, /* MaxPower 0 mA */

/*---------------------------------------------------------------------------*/
/*Interface Association Descriptor*/
0x08, /* bLength: Descriptor length */
0x0B, /* bDescriptorType: IAD */
0x00, /* bFirstInterface */
0x02, /* bInterfaceCount */
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
/* TODO: Put a meaningful string here, which shows up in the Windows */
/* device manager when no driver is installed yet. */
0x00, /* iFunction */

/*---------------------------------------------------------------------------*/
/* Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
Expand Down Expand Up @@ -233,7 +246,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
0x00 /* bInterval: ignore for Bulk transfer */
0x00, /* bInterval: ignore for Bulk transfer */

DFU_RT_IFACE_DESC,
};


Expand All @@ -244,11 +259,25 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
0x00,
0x02, /* bNumInterfaces: 2 interface */
0x03, /* bNumInterfaces: 3 interface */
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: self powered */
0x32, /* MaxPower 0 mA */

/*---------------------------------------------------------------------------*/
/*Interface Association Descriptor*/
0x08, /* bLength: Descriptor length */
0x0B, /* bDescriptorType: IAD */
0x00, /* bFirstInterface */
0x02, /* bInterfaceCount */
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
/* TODO: Put a meaningful string here, which shows up in the Windows */
/* device manager when no driver is installed yet. */
0x00, /* iFunction */

/*---------------------------------------------------------------------------*/
/* Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
Expand Down Expand Up @@ -318,19 +347,36 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00 /* bInterval: ignore for Bulk transfer */
0x00, /* bInterval: ignore for Bulk transfer */

DFU_RT_IFACE_DESC,
};

__ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = {
0x09, /* bLength: Configuation Descriptor size */
USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION,
USB_CDC_CONFIG_DESC_SIZ,
0x00,
0x02, /* bNumInterfaces: 2 interfaces */
0x03, /* bNumInterfaces: 3 interfaces */
0x01, /* bConfigurationValue: */
0x04, /* iConfiguration: */
0xC0, /* bmAttributes: */
0x32, /* MaxPower 100 mA */

/*---------------------------------------------------------------------------*/
/*Interface Association Descriptor*/
0x08, /* bLength: Descriptor length */
0x0B, /* bDescriptorType: IAD */
0x00, /* bFirstInterface */
0x02, /* bInterfaceCount */
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
/* TODO: Put a meaningful string here, which shows up in the Windows */
/* device manager when no driver is installed yet. */
0x00, /* iFunction */

/*---------------------------------------------------------------------------*/
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
Expand Down Expand Up @@ -399,7 +445,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ]
0x02, /* bmAttributes: Bulk */
0x40, /* wMaxPacketSize: */
0x00,
0x00 /* bInterval */
0x00, /* bInterval */

DFU_RT_IFACE_DESC,
};

/**
Expand Down Expand Up @@ -539,7 +587,26 @@ static uint8_t USBD_CDC_Setup(USBD_HandleTypeDef *pdev,

switch (req->bmRequest & USB_REQ_TYPE_MASK) {
case USB_REQ_TYPE_CLASS:
if (req->wLength != 0U) {
if ((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE
&& req->wIndex == DFU_RT_IFACE_NUM) {
// Handle requests to the DFU interface separately
int device_to_host = (req->bmRequest & 0x80U);

if (!device_to_host && req->wLength > 0) {
// When data is sent, return an error, since the data receiving
// machinery will forget the target interface and handle as a CDC
// request instead.
ret = USBD_FAIL;
} else {
ret = USBD_DFU_Runtime_Control(req->bRequest, req->wValue, (uint8_t *)(void *)hcdc->data, req->wLength);
}

if (ret == USBD_FAIL) {
USBD_CtlError(pdev, req);
} else if (device_to_host && req->wLength > 0) {
USBD_CtlSendData(pdev, (uint8_t *)(void *)hcdc->data, req->wLength);
}
} else if (req->wLength != 0U) {
if ((req->bmRequest & 0x80U) != 0U) {
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,
(uint8_t *)hcdc->data,
Expand Down
3 changes: 2 additions & 1 deletion cores/arduino/stm32/usb/cdc/usbd_cdc.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ extern "C" {
/* Includes ------------------------------------------------------------------*/
#include "usbd_ioreq.h"
#include "usbd_ep_conf.h"
#include "dfu_runtime.h"

/** @addtogroup STM32_USB_DEVICE_LIBRARY
* @{
Expand All @@ -51,7 +52,7 @@ extern "C" {

/* CDC Endpoints parameters */

#define USB_CDC_CONFIG_DESC_SIZ 67U
#define USB_CDC_CONFIG_DESC_SIZ 67U + /* IAD */ 8 + DFU_RT_IFACE_DESC_SIZE
#define CDC_DATA_HS_IN_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE
#define CDC_DATA_HS_OUT_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE

Expand Down
151 changes: 151 additions & 0 deletions cores/arduino/stm32/usb/dfu_runtime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USB_DFU_RUNTIME_H
#define __USB_DFU_RUNTIME_H

#include <bootloader.h>

/**************************************************/
/* DFU Requests DFU states */
/**************************************************/
#define APP_STATE_IDLE 0U
#define APP_STATE_DETACH 1U
#define DFU_STATE_IDLE 2U
#define DFU_STATE_DNLOAD_SYNC 3U
#define DFU_STATE_DNLOAD_BUSY 4U
#define DFU_STATE_DNLOAD_IDLE 5U
#define DFU_STATE_MANIFEST_SYNC 6U
#define DFU_STATE_MANIFEST 7U
#define DFU_STATE_MANIFEST_WAIT_RESET 8U
#define DFU_STATE_UPLOAD_IDLE 9U
#define DFU_STATE_ERROR 10U

/**************************************************/
/* DFU errors */
/**************************************************/
#define DFU_ERROR_NONE 0x00U
#define DFU_ERROR_TARGET 0x01U
#define DFU_ERROR_FILE 0x02U
#define DFU_ERROR_WRITE 0x03U
#define DFU_ERROR_ERASE 0x04U
#define DFU_ERROR_CHECK_ERASED 0x05U
#define DFU_ERROR_PROG 0x06U
#define DFU_ERROR_VERIFY 0x07U
#define DFU_ERROR_ADDRESS 0x08U
#define DFU_ERROR_NOTDONE 0x09U
#define DFU_ERROR_FIRMWARE 0x0AU
#define DFU_ERROR_VENDOR 0x0BU
#define DFU_ERROR_USB 0x0CU
#define DFU_ERROR_POR 0x0DU
#define DFU_ERROR_UNKNOWN 0x0EU
#define DFU_ERROR_STALLEDPKT 0x0FU

typedef enum {
DFU_DETACH = 0U,
DFU_DNLOAD,
DFU_UPLOAD,
DFU_GETSTATUS,
DFU_CLRSTATUS,
DFU_GETSTATE,
DFU_ABORT
} DFU_RequestTypeDef;

#define DFU_DESCRIPTOR_TYPE 0x21U

// Device will detach by itself (alternative is that the host sends a
// USB reset within DETACH_TIMEOUT).
#define DFU_RT_ATTR_WILL_DETACH 0x08U
// Device is still accessible on USB after flashing (manifestation).
// Probably not so relevant in runtime mode
#define DFU_RT_ATTR_MANIFESTATION_TOLERANT 0x04U
#define DFU_RT_ATTR_CAN_UPLOAD 0x02U
#define DFU_RT_ATTR_CAN_DNLOAD 0x01U

// Of these, only WILL_DETACH is relevant at runtime, but specify
// CAN_UPLOAD and CAN_DNLOAD too, just in case there is a tool that
// somehow checks these before resetting.
#define DFU_RT_ATTRS DFU_RT_ATTR_WILL_DETACH \
| DFU_RT_ATTR_CAN_UPLOAD | DFU_RT_ATTR_CAN_DNLOAD

// Detach timeout is only relevant when ATTR_WILL_DETACH is unset
#define DFU_RT_DETACH_TIMEOUT 0
// This should be only relevant for actual firmware uploads (the actual
// value is read from the bootloader after reset), but specify a
// conservative value here in case any tool fails to reread the value
// after reset.
// The max packet size for EP0 control transfers is specified in the
// device descriptor.
#define DFU_RT_TRANSFER_SIZE 64
#define DFU_RT_DFU_VERSION 0x0101 // DFU 1.1

#define DFU_RT_IFACE_NUM 2 // XXX: Hardcoded

#define DFU_RT_IFACE_DESC_SIZE 18U
#define DFU_RT_IFACE_DESC \
/*DFU Runtime interface descriptor*/ \
0x09, /* bLength: Endpoint Descriptor size */ \
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ \
DFU_RT_IFACE_NUM, /* bInterfaceNumber: Number of Interface */ \
0x00, /* bAlternateSetting: Alternate setting */ \
0x00, /* bNumEndpoints: no endpoints used (only control endpoint) */ \
0xFE, /* bInterfaceClass: Application Specific */ \
0x01, /* bInterfaceSubClass: Device Firmware Upgrade Code*/ \
0x01, /* bInterfaceProtocol: Runtime Protocol*/ \
/* TODO: Put a meaningful string here, which shows up in the Windows * */ \
/* device manager when no driver is installed yet. */ \
0x00, /* iInterface: */ \
\
/*DFU Runtime Functional Descriptor*/ \
0x09, /* bFunctionLength */ \
DFU_DESCRIPTOR_TYPE, /* bDescriptorType: DFU Functional */ \
DFU_RT_ATTRS, /* bmAttributes: DFU Attributes */ \
LOBYTE(DFU_RT_DETACH_TIMEOUT), /* wDetachTimeout */ \
HIBYTE(DFU_RT_DETACH_TIMEOUT), \
LOBYTE(DFU_RT_TRANSFER_SIZE), /* wTransferSize */ \
HIBYTE(DFU_RT_TRANSFER_SIZE), \
LOBYTE(DFU_RT_DFU_VERSION), /* bcdDFUVersion */ \
HIBYTE(DFU_RT_DFU_VERSION)

/**
* @brief USBD_DFU_Runtime_Control
* Manage the DFU interface control requests
* @param bRequest: Command code from request
* @param wValue: Value from request
* @param data: Buffer for result
* @param length: Number of data to be sent (in bytes)
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/
static int8_t USBD_DFU_Runtime_Control(uint8_t bRequest, uint16_t wValue, uint8_t *data, uint16_t len)
{
UNUSED(wValue);
switch (bRequest) {
case DFU_GETSTATUS:
if (len != 6) {
return (USBD_FAIL);
}

data[0] = DFU_ERROR_NONE;
// Minimum delay until next GET_STATUS
data[1] = data[2] = data[3] = 0;
data[4] = APP_STATE_IDLE;
// State string descriptor
data[5] = 0;

return (USBD_OK);

case DFU_DETACH:
scheduleBootloaderReset();
return (USBD_OK);

case DFU_GETSTATE:
if (len != 1) {
return (USBD_FAIL);
}
data[0] = APP_STATE_IDLE;
return (USBD_OK);

default:
return (USBD_FAIL);
}
}

#endif // __USB_DFU_RUNTIME_H
2 changes: 1 addition & 1 deletion cores/arduino/stm32/usb/usbd_conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ extern "C" {
#endif

#ifndef USBD_MAX_NUM_INTERFACES
#define USBD_MAX_NUM_INTERFACES 2U
#define USBD_MAX_NUM_INTERFACES 3U
#endif /* USBD_MAX_NUM_INTERFACES */

#ifndef USBD_MAX_NUM_CONFIGURATION
Expand Down
6 changes: 3 additions & 3 deletions cores/arduino/stm32/usb/usbd_desc.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,9 @@ __ALIGN_BEGIN uint8_t USBD_Class_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
0x00, /* bcdUSB */
#endif
0x02,
0x02, /* bDeviceClass */
0x02, /* bDeviceSubClass */
0x00, /* bDeviceProtocol */
0xEF, /* bDeviceClass (Miscellaneous) */
0x02, /* bDeviceSubClass (Common Class) */
0x01, /* bDeviceProtocol (Interface Association Descriptor) */
USB_MAX_EP0_SIZE, /* bMaxPacketSize */
LOBYTE(USBD_VID), /* idVendor */
HIBYTE(USBD_VID), /* idVendor */
Expand Down

0 comments on commit 1173c3d

Please sign in to comment.