Skip to content

Commit 91a8700

Browse files
[USB] Support DFU runtime protocol along CDC
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 stm32duino#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. - The DFU attributes in the descriptor are hardcoded (while they should probably match the values exposed by the bootloader, so probably be defined by boards.txt). - DFU support is unconditionally advertised, while not all boards might support DFU.
1 parent a692ebd commit 91a8700

File tree

5 files changed

+226
-14
lines changed

5 files changed

+226
-14
lines changed

cores/arduino/stm32/usb/cdc/usbd_cdc.c

+68-7
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,24 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
173173
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
174174
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
175175
0x00,
176-
0x02, /* bNumInterfaces: 2 interface */
176+
0x03, /* bNumInterfaces: 3 interface */
177177
0x01, /* bConfigurationValue: Configuration value */
178178
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
179179
0xC0, /* bmAttributes: self powered */
180180
0x32, /* MaxPower 0 mA */
181181

182+
/*Interface Association Descriptor*/
183+
0x08, // bLength: Descriptor length
184+
0x0B, // bDescriptorType: IAD
185+
0x00, // bFirstInterface
186+
0x02, // bInterfaceCount
187+
0x02, // bFunctionClass (class of subdevice, should match first interface)
188+
0x02, // bFunctionSubclass (subclass of subdevice, should match first interface)
189+
0x00, // bFunctionProtocol (protocol of subdevice, should match first interface)
190+
/* TODO: Put a meaningful string here, which shows up in the Windows * */
191+
/* device manager when no driver is installed yet. */
192+
0x00, // iFunction
193+
182194
/*---------------------------------------------------------------------------*/
183195

184196
/*Interface Descriptor */
@@ -257,7 +269,9 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
257269
0x02, /* bmAttributes: Bulk */
258270
LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
259271
HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
260-
0x00 /* bInterval: ignore for Bulk transfer */
272+
0x00, /* bInterval: ignore for Bulk transfer */
273+
274+
DFU_RT_IFACE_DESC
261275
} ;
262276

263277

@@ -268,14 +282,26 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
268282
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
269283
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
270284
0x00,
271-
0x02, /* bNumInterfaces: 2 interface */
285+
0x03, /* bNumInterfaces: 3 interface */
272286
0x01, /* bConfigurationValue: Configuration value */
273287
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
274288
0xC0, /* bmAttributes: self powered */
275289
0x32, /* MaxPower 0 mA */
276290

277291
/*---------------------------------------------------------------------------*/
278292

293+
/*Interface Association Descriptor*/
294+
0x08, // bLength: Descriptor length
295+
0x0B, // bDescriptorType: IAD
296+
0x00, // bFirstInterface
297+
0x02, // bInterfaceCount
298+
0x02, // bFunctionClass (class of subdevice, should match first interface)
299+
0x02, // bFunctionSubclass (subclass of subdevice, should match first interface)
300+
0x00, // bFunctionProtocol (protocol of subdevice, should match first interface)
301+
/* TODO: Put a meaningful string here, which shows up in the Windows * */
302+
/* device manager when no driver is installed yet. */
303+
0x00, // iFunction
304+
279305
/*Interface Descriptor */
280306
0x09, /* bLength: Interface Descriptor size */
281307
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
@@ -352,20 +378,34 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
352378
0x02, /* bmAttributes: Bulk */
353379
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
354380
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
355-
0x00 /* bInterval: ignore for Bulk transfer */
381+
0x00, /* bInterval: ignore for Bulk transfer */
382+
383+
DFU_RT_IFACE_DESC
356384
} ;
357385

358386
__ALIGN_BEGIN uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = {
359387
0x09, /* bLength: Configuation Descriptor size */
360388
USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION,
361389
USB_CDC_CONFIG_DESC_SIZ,
362390
0x00,
363-
0x02, /* bNumInterfaces: 2 interfaces */
391+
0x03, /* bNumInterfaces: 3 interfaces */
364392
0x01, /* bConfigurationValue: */
365393
0x04, /* iConfiguration: */
366394
0xC0, /* bmAttributes: */
367395
0x32, /* MaxPower 100 mA */
368396

397+
/*Interface Association Descriptor*/
398+
0x08, // bLength: Descriptor length
399+
0x0B, // bDescriptorType: IAD
400+
0x00, // bFirstInterface
401+
0x02, // bInterfaceCount
402+
0x02, // bFunctionClass (class of subdevice, should match first interface)
403+
0x02, // bFunctionSubclass (subclass of subdevice, should match first interface)
404+
0x00, // bFunctionProtocol (protocol of subdevice, should match first interface)
405+
/* TODO: Put a meaningful string here, which shows up in the Windows * */
406+
/* device manager when no driver is installed yet. */
407+
0x00, // iFunction
408+
369409
/*Interface Descriptor */
370410
0x09, /* bLength: Interface Descriptor size */
371411
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
@@ -443,7 +483,9 @@ __ALIGN_BEGIN uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIG
443483
0x02, /* bmAttributes: Bulk */
444484
0x40, /* wMaxPacketSize: */
445485
0x00,
446-
0x00 /* bInterval */
486+
0x00, /* bInterval */
487+
488+
DFU_RT_IFACE_DESC
447489
};
448490

449491
/**
@@ -575,7 +617,26 @@ static uint8_t USBD_CDC_Setup(USBD_HandleTypeDef *pdev,
575617

576618
switch (req->bmRequest & USB_REQ_TYPE_MASK) {
577619
case USB_REQ_TYPE_CLASS :
578-
if (req->wLength) {
620+
if ((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE
621+
&& req->wIndex == DFU_RT_IFACE_NUM) {
622+
// Handle requests to the DFU interface separately
623+
int device_to_host = (req->bmRequest & 0x80U);
624+
625+
if (!device_to_host && req->wLength > 0) {
626+
// When data is sent, return an error, since the data receiving
627+
// machinery will forget the target interface and handle as a CDC
628+
// request instead.
629+
ret = USBD_FAIL;
630+
} else {
631+
ret = USBD_DFU_Runtime_Control(req->bRequest, req->wValue, (uint8_t *)(void *)hcdc->data, req->wLength);
632+
}
633+
634+
if (ret == USBD_FAIL) {
635+
USBD_CtlError(pdev, req);
636+
} else if (device_to_host && req->wLength > 0) {
637+
USBD_CtlSendData(pdev, (uint8_t *)(void *)hcdc->data, req->wLength);
638+
}
639+
} else if (req->wLength) {
579640
if (req->bmRequest & 0x80U) {
580641
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,
581642
(uint8_t *)(void *)hcdc->data,

cores/arduino/stm32/usb/cdc/usbd_cdc.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ extern "C" {
2828
/* Includes ------------------------------------------------------------------*/
2929
#include "usbd_ioreq.h"
3030
#include "usbd_ep_conf.h"
31+
#include "dfu_runtime.h"
3132

3233
/** @addtogroup STM32_USB_DEVICE_LIBRARY
3334
* @{
@@ -51,7 +52,7 @@ extern "C" {
5152

5253
/* CDC Endpoints parameters */
5354

54-
#define USB_CDC_CONFIG_DESC_SIZ 67U
55+
#define USB_CDC_CONFIG_DESC_SIZ 67U + /* IAD */ 8 + DFU_RT_IFACE_DESC_SIZE
5556
#define CDC_DATA_HS_IN_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE
5657
#define CDC_DATA_HS_OUT_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE
5758

cores/arduino/stm32/usb/dfu_runtime.h

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/* Define to prevent recursive inclusion -------------------------------------*/
2+
#ifndef __USB_DFU_RUNTIME_H
3+
#define __USB_DFU_RUNTIME_H
4+
5+
#include <bootloader.h>
6+
7+
/**************************************************/
8+
/* DFU Requests DFU states */
9+
/**************************************************/
10+
#define APP_STATE_IDLE 0U
11+
#define APP_STATE_DETACH 1U
12+
#define DFU_STATE_IDLE 2U
13+
#define DFU_STATE_DNLOAD_SYNC 3U
14+
#define DFU_STATE_DNLOAD_BUSY 4U
15+
#define DFU_STATE_DNLOAD_IDLE 5U
16+
#define DFU_STATE_MANIFEST_SYNC 6U
17+
#define DFU_STATE_MANIFEST 7U
18+
#define DFU_STATE_MANIFEST_WAIT_RESET 8U
19+
#define DFU_STATE_UPLOAD_IDLE 9U
20+
#define DFU_STATE_ERROR 10U
21+
22+
/**************************************************/
23+
/* DFU errors */
24+
/**************************************************/
25+
#define DFU_ERROR_NONE 0x00U
26+
#define DFU_ERROR_TARGET 0x01U
27+
#define DFU_ERROR_FILE 0x02U
28+
#define DFU_ERROR_WRITE 0x03U
29+
#define DFU_ERROR_ERASE 0x04U
30+
#define DFU_ERROR_CHECK_ERASED 0x05U
31+
#define DFU_ERROR_PROG 0x06U
32+
#define DFU_ERROR_VERIFY 0x07U
33+
#define DFU_ERROR_ADDRESS 0x08U
34+
#define DFU_ERROR_NOTDONE 0x09U
35+
#define DFU_ERROR_FIRMWARE 0x0AU
36+
#define DFU_ERROR_VENDOR 0x0BU
37+
#define DFU_ERROR_USB 0x0CU
38+
#define DFU_ERROR_POR 0x0DU
39+
#define DFU_ERROR_UNKNOWN 0x0EU
40+
#define DFU_ERROR_STALLEDPKT 0x0FU
41+
42+
typedef enum
43+
{
44+
DFU_DETACH = 0U,
45+
DFU_DNLOAD,
46+
DFU_UPLOAD,
47+
DFU_GETSTATUS,
48+
DFU_CLRSTATUS,
49+
DFU_GETSTATE,
50+
DFU_ABORT
51+
} DFU_RequestTypeDef;
52+
53+
#define DFU_DESCRIPTOR_TYPE 0x21U
54+
55+
// Device will detach by itself (alternative is that the host sends a
56+
// USB reset within DETACH_TIMEOUT).
57+
#define DFU_RT_ATTR_WILL_DETACH 0x08U
58+
// Device is still accessible on USB after flashing (manifestation).
59+
// Probably not so relevant in runtime mode
60+
#define DFU_RT_ATTR_MANIFESTATION_TOLERANT 0x04U
61+
#define DFU_RT_ATTR_CAN_UPLOAD 0x02U
62+
#define DFU_RT_ATTR_CAN_DNLOAD 0x01U
63+
64+
// Of these, only WILL_DETACH is relevant at runtime, but specify
65+
// CAN_UPLOAD and CAN_DNLOAD too, just in case there is a tool that
66+
// somehow checks these before resetting.
67+
#define DFU_RT_ATTRS DFU_RT_ATTR_WILL_DETACH \
68+
| DFU_RT_ATTR_CAN_UPLOAD | DFU_RT_ATTR_CAN_DNLOAD
69+
70+
// Detach timeout is only relevant when ATTR_WILL_DETACH is unset
71+
#define DFU_RT_DETACH_TIMEOUT 0
72+
// This should be only relevant for actual firmware uploads (the actual
73+
// value is read from the bootloader after reset), but specify a
74+
// conservative value here in case any tool fails to reread the value
75+
// after reset.
76+
// The max packet size for EP0 control transfers is specified in the
77+
// device descriptor.
78+
#define DFU_RT_TRANSFER_SIZE 64
79+
#define DFU_RT_DFU_VERSION 0x0101 // DFU 1.1
80+
81+
#define DFU_RT_IFACE_NUM 2 // XXX: Hardcoded
82+
83+
#define DFU_RT_IFACE_DESC_SIZE 18U
84+
#define DFU_RT_IFACE_DESC \
85+
/*DFU Runtime interface descriptor*/ \
86+
0x09, /* bLength: Endpoint Descriptor size */ \
87+
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ \
88+
DFU_RT_IFACE_NUM, /* bInterfaceNumber: Number of Interface */ \
89+
0x00, /* bAlternateSetting: Alternate setting */ \
90+
0x00, /* bNumEndpoints: no endpoints used (only control endpoint) */ \
91+
0xFE, /* bInterfaceClass: Application Specific */ \
92+
0x01, /* bInterfaceSubClass: Device Firmware Upgrade Code*/ \
93+
0x01, /* bInterfaceProtocol: Runtime Protocol*/ \
94+
/* TODO: Put a meaningful string here, which shows up in the Windows * */ \
95+
/* device manager when no driver is installed yet. */ \
96+
0x00, /* iInterface: */ \
97+
\
98+
/*DFU Runtime Functional Descriptor*/ \
99+
0x09, /* bFunctionLength */ \
100+
DFU_DESCRIPTOR_TYPE, /* bDescriptorType: DFU Functional */ \
101+
DFU_RT_ATTRS, /* bmAttributes: DFU Attributes */ \
102+
LOBYTE(DFU_RT_DETACH_TIMEOUT), /* wDetachTimeout */ \
103+
HIBYTE(DFU_RT_DETACH_TIMEOUT), \
104+
LOBYTE(DFU_RT_TRANSFER_SIZE), /* wTransferSize */ \
105+
HIBYTE(DFU_RT_TRANSFER_SIZE), \
106+
LOBYTE(DFU_RT_DFU_VERSION), /* bcdDFUVersion */ \
107+
HIBYTE(DFU_RT_DFU_VERSION)
108+
109+
/**
110+
* @brief USBD_DFU_Runtime_Control
111+
* Manage the DFU interface control requests
112+
* @param bRequest: Command code from request
113+
* @param wValue: Value from request
114+
* @param data: Buffer for result
115+
* @param length: Number of data to be sent (in bytes)
116+
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
117+
*/
118+
static int8_t USBD_DFU_Runtime_Control(uint8_t bRequest, uint16_t wValue, uint8_t *data, uint16_t len) {
119+
UNUSED(wValue);
120+
switch (bRequest) {
121+
case DFU_GETSTATUS:
122+
if (len != 6)
123+
return (USBD_FAIL);
124+
125+
data[0] = DFU_ERROR_NONE;
126+
// Minimum delay until next GET_STATUS
127+
data[1] = data[2] = data[3] = 0;
128+
data[4] = APP_STATE_IDLE;
129+
// State string descriptor
130+
data[5] = 0;
131+
132+
return (USBD_OK);
133+
134+
case DFU_DETACH:
135+
// TODO: Delay?
136+
jumpToBootloaderRequested();
137+
return (USBD_OK);
138+
139+
case DFU_GETSTATE:
140+
if (len != 1)
141+
return (USBD_FAIL);
142+
data[0] = APP_STATE_IDLE;
143+
return (USBD_OK);
144+
145+
default:
146+
return (USBD_FAIL);
147+
}
148+
}
149+
150+
#endif // __USB_DFU_RUNTIME_H

cores/arduino/stm32/usb/usbd_conf.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ extern "C" {
7070
#endif
7171

7272
#ifndef USBD_MAX_NUM_INTERFACES
73-
#define USBD_MAX_NUM_INTERFACES 2U
73+
#define USBD_MAX_NUM_INTERFACES 3U
7474
#endif /* USBD_MAX_NUM_INTERFACES */
7575

7676
#ifndef USBD_MAX_STR_DESC_SIZ

cores/arduino/stm32/usb/usbd_desc.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ __ALIGN_BEGIN uint8_t USBD_Class_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
9393
USB_DESC_TYPE_DEVICE, /* bDescriptorType */
9494
0x00, /* bcdUSB */
9595
0x02,
96-
0x00, /* bDeviceClass */
97-
0x00, /* bDeviceSubClass */
98-
0x00, /* bDeviceProtocol */
96+
0xEF, /* bDeviceClass (Miscellaneous) */
97+
0x02, /* bDeviceSubClass (Common Class) */
98+
0x01, /* bDeviceProtocol (Interface Association Descriptor) */
9999
USB_MAX_EP0_SIZE, /* bMaxPacketSize */
100100
LOBYTE(USBD_VID), /* idVendor */
101101
HIBYTE(USBD_VID), /* idVendor */
@@ -117,8 +117,8 @@ __ALIGN_BEGIN uint8_t USBD_Class_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
117117
USB_DESC_TYPE_DEVICE, /* bDescriptorType */
118118
0x00, /* bcdUSB */
119119
0x02,
120-
0x02, /* bDeviceClass */
121-
0x02, /* bDeviceSubClass */
120+
0x00, /* bDeviceClass */
121+
0x00, /* bDeviceSubClass */
122122
0x00, /* bDeviceProtocol */
123123
USB_MAX_EP0_SIZE, /* bMaxPacketSize */
124124
LOBYTE(USBD_VID), /* idVendor */

0 commit comments

Comments
 (0)