Skip to content

Commit 155f6c0

Browse files
matthijskooijmanfpistm
authored andcommitted
[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. - DFU support is unconditionally advertised, while not all boards might support DFU.
1 parent 690d122 commit 155f6c0

File tree

5 files changed

+231
-12
lines changed

5 files changed

+231
-12
lines changed

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

+74-7
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,26 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
158158
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
159159
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
160160
0x00,
161-
0x02, /* bNumInterfaces: 2 interface */
161+
0x03, /* bNumInterfaces: 3 interface */
162162
0x01, /* bConfigurationValue: Configuration value */
163163
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
164164
0xC0, /* bmAttributes: self powered */
165165
0x32, /* MaxPower 0 mA */
166+
166167
/*---------------------------------------------------------------------------*/
168+
/*Interface Association Descriptor*/
169+
0x08, /* bLength: Descriptor length */
170+
0x0B, /* bDescriptorType: IAD */
171+
0x00, /* bFirstInterface */
172+
0x02, /* bInterfaceCount */
173+
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
174+
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
175+
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
176+
/* TODO: Put a meaningful string here, which shows up in the Windows */
177+
/* device manager when no driver is installed yet. */
178+
0x00, /* iFunction */
167179

180+
/*---------------------------------------------------------------------------*/
168181
/* Interface Descriptor */
169182
0x09, /* bLength: Interface Descriptor size */
170183
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
@@ -233,7 +246,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
233246
0x02, /* bmAttributes: Bulk */
234247
LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
235248
HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
236-
0x00 /* bInterval: ignore for Bulk transfer */
249+
0x00, /* bInterval: ignore for Bulk transfer */
250+
251+
DFU_RT_IFACE_DESC,
237252
};
238253

239254

@@ -244,11 +259,25 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
244259
USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
245260
USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */
246261
0x00,
247-
0x02, /* bNumInterfaces: 2 interface */
262+
0x03, /* bNumInterfaces: 3 interface */
248263
0x01, /* bConfigurationValue: Configuration value */
249264
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
250265
0xC0, /* bmAttributes: self powered */
251266
0x32, /* MaxPower 0 mA */
267+
268+
/*---------------------------------------------------------------------------*/
269+
/*Interface Association Descriptor*/
270+
0x08, /* bLength: Descriptor length */
271+
0x0B, /* bDescriptorType: IAD */
272+
0x00, /* bFirstInterface */
273+
0x02, /* bInterfaceCount */
274+
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
275+
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
276+
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
277+
/* TODO: Put a meaningful string here, which shows up in the Windows */
278+
/* device manager when no driver is installed yet. */
279+
0x00, /* iFunction */
280+
252281
/*---------------------------------------------------------------------------*/
253282
/* Interface Descriptor */
254283
0x09, /* bLength: Interface Descriptor size */
@@ -318,19 +347,36 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN
318347
0x02, /* bmAttributes: Bulk */
319348
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
320349
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
321-
0x00 /* bInterval: ignore for Bulk transfer */
350+
0x00, /* bInterval: ignore for Bulk transfer */
351+
352+
DFU_RT_IFACE_DESC,
322353
};
323354

324355
__ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = {
325356
0x09, /* bLength: Configuation Descriptor size */
326357
USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION,
327358
USB_CDC_CONFIG_DESC_SIZ,
328359
0x00,
329-
0x02, /* bNumInterfaces: 2 interfaces */
360+
0x03, /* bNumInterfaces: 3 interfaces */
330361
0x01, /* bConfigurationValue: */
331362
0x04, /* iConfiguration: */
332363
0xC0, /* bmAttributes: */
333364
0x32, /* MaxPower 100 mA */
365+
366+
/*---------------------------------------------------------------------------*/
367+
/*Interface Association Descriptor*/
368+
0x08, /* bLength: Descriptor length */
369+
0x0B, /* bDescriptorType: IAD */
370+
0x00, /* bFirstInterface */
371+
0x02, /* bInterfaceCount */
372+
0x02, /* bFunctionClass (class of subdevice, should match first interface) */
373+
0x02, /* bFunctionSubclass (subclass of subdevice, should match first interface) */
374+
0x00, /* bFunctionProtocol (protocol of subdevice, should match first interface) */
375+
/* TODO: Put a meaningful string here, which shows up in the Windows */
376+
/* device manager when no driver is installed yet. */
377+
0x00, /* iFunction */
378+
379+
/*---------------------------------------------------------------------------*/
334380
/*Interface Descriptor */
335381
0x09, /* bLength: Interface Descriptor size */
336382
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
@@ -399,7 +445,9 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ]
399445
0x02, /* bmAttributes: Bulk */
400446
0x40, /* wMaxPacketSize: */
401447
0x00,
402-
0x00 /* bInterval */
448+
0x00, /* bInterval */
449+
450+
DFU_RT_IFACE_DESC,
403451
};
404452

405453
/**
@@ -539,7 +587,26 @@ static uint8_t USBD_CDC_Setup(USBD_HandleTypeDef *pdev,
539587

540588
switch (req->bmRequest & USB_REQ_TYPE_MASK) {
541589
case USB_REQ_TYPE_CLASS:
542-
if (req->wLength != 0U) {
590+
if ((req->bmRequest & USB_REQ_RECIPIENT_MASK) == USB_REQ_RECIPIENT_INTERFACE
591+
&& req->wIndex == DFU_RT_IFACE_NUM) {
592+
// Handle requests to the DFU interface separately
593+
int device_to_host = (req->bmRequest & 0x80U);
594+
595+
if (!device_to_host && req->wLength > 0) {
596+
// When data is sent, return an error, since the data receiving
597+
// machinery will forget the target interface and handle as a CDC
598+
// request instead.
599+
ret = USBD_FAIL;
600+
} else {
601+
ret = USBD_DFU_Runtime_Control(req->bRequest, req->wValue, (uint8_t *)(void *)hcdc->data, req->wLength);
602+
}
603+
604+
if (ret == USBD_FAIL) {
605+
USBD_CtlError(pdev, req);
606+
} else if (device_to_host && req->wLength > 0) {
607+
USBD_CtlSendData(pdev, (uint8_t *)(void *)hcdc->data, req->wLength);
608+
}
609+
} else if (req->wLength != 0U) {
543610
if ((req->bmRequest & 0x80U) != 0U) {
544611
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Control(req->bRequest,
545612
(uint8_t *)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

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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+
DFU_DETACH = 0U,
44+
DFU_DNLOAD,
45+
DFU_UPLOAD,
46+
DFU_GETSTATUS,
47+
DFU_CLRSTATUS,
48+
DFU_GETSTATE,
49+
DFU_ABORT
50+
} DFU_RequestTypeDef;
51+
52+
#define DFU_DESCRIPTOR_TYPE 0x21U
53+
54+
// Device will detach by itself (alternative is that the host sends a
55+
// USB reset within DETACH_TIMEOUT).
56+
#define DFU_RT_ATTR_WILL_DETACH 0x08U
57+
// Device is still accessible on USB after flashing (manifestation).
58+
// Probably not so relevant in runtime mode
59+
#define DFU_RT_ATTR_MANIFESTATION_TOLERANT 0x04U
60+
#define DFU_RT_ATTR_CAN_UPLOAD 0x02U
61+
#define DFU_RT_ATTR_CAN_DNLOAD 0x01U
62+
63+
// Of these, only WILL_DETACH is relevant at runtime, but specify
64+
// CAN_UPLOAD and CAN_DNLOAD too, just in case there is a tool that
65+
// somehow checks these before resetting.
66+
#define DFU_RT_ATTRS DFU_RT_ATTR_WILL_DETACH \
67+
| DFU_RT_ATTR_CAN_UPLOAD | DFU_RT_ATTR_CAN_DNLOAD
68+
69+
// Detach timeout is only relevant when ATTR_WILL_DETACH is unset
70+
#define DFU_RT_DETACH_TIMEOUT 0
71+
// This should be only relevant for actual firmware uploads (the actual
72+
// value is read from the bootloader after reset), but specify a
73+
// conservative value here in case any tool fails to reread the value
74+
// after reset.
75+
// The max packet size for EP0 control transfers is specified in the
76+
// device descriptor.
77+
#define DFU_RT_TRANSFER_SIZE 64
78+
#define DFU_RT_DFU_VERSION 0x0101 // DFU 1.1
79+
80+
#define DFU_RT_IFACE_NUM 2 // XXX: Hardcoded
81+
82+
#define DFU_RT_IFACE_DESC_SIZE 18U
83+
#define DFU_RT_IFACE_DESC \
84+
/*DFU Runtime interface descriptor*/ \
85+
0x09, /* bLength: Endpoint Descriptor size */ \
86+
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ \
87+
DFU_RT_IFACE_NUM, /* bInterfaceNumber: Number of Interface */ \
88+
0x00, /* bAlternateSetting: Alternate setting */ \
89+
0x00, /* bNumEndpoints: no endpoints used (only control endpoint) */ \
90+
0xFE, /* bInterfaceClass: Application Specific */ \
91+
0x01, /* bInterfaceSubClass: Device Firmware Upgrade Code*/ \
92+
0x01, /* bInterfaceProtocol: Runtime Protocol*/ \
93+
/* TODO: Put a meaningful string here, which shows up in the Windows * */ \
94+
/* device manager when no driver is installed yet. */ \
95+
0x00, /* iInterface: */ \
96+
\
97+
/*DFU Runtime Functional Descriptor*/ \
98+
0x09, /* bFunctionLength */ \
99+
DFU_DESCRIPTOR_TYPE, /* bDescriptorType: DFU Functional */ \
100+
DFU_RT_ATTRS, /* bmAttributes: DFU Attributes */ \
101+
LOBYTE(DFU_RT_DETACH_TIMEOUT), /* wDetachTimeout */ \
102+
HIBYTE(DFU_RT_DETACH_TIMEOUT), \
103+
LOBYTE(DFU_RT_TRANSFER_SIZE), /* wTransferSize */ \
104+
HIBYTE(DFU_RT_TRANSFER_SIZE), \
105+
LOBYTE(DFU_RT_DFU_VERSION), /* bcdDFUVersion */ \
106+
HIBYTE(DFU_RT_DFU_VERSION)
107+
108+
/**
109+
* @brief USBD_DFU_Runtime_Control
110+
* Manage the DFU interface control requests
111+
* @param bRequest: Command code from request
112+
* @param wValue: Value from request
113+
* @param data: Buffer for result
114+
* @param length: Number of data to be sent (in bytes)
115+
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
116+
*/
117+
static int8_t USBD_DFU_Runtime_Control(uint8_t bRequest, uint16_t wValue, uint8_t *data, uint16_t len)
118+
{
119+
UNUSED(wValue);
120+
switch (bRequest) {
121+
case DFU_GETSTATUS:
122+
if (len != 6) {
123+
return (USBD_FAIL);
124+
}
125+
126+
data[0] = DFU_ERROR_NONE;
127+
// Minimum delay until next GET_STATUS
128+
data[1] = data[2] = data[3] = 0;
129+
data[4] = APP_STATE_IDLE;
130+
// State string descriptor
131+
data[5] = 0;
132+
133+
return (USBD_OK);
134+
135+
case DFU_DETACH:
136+
scheduleBootloaderReset();
137+
return (USBD_OK);
138+
139+
case DFU_GETSTATE:
140+
if (len != 1) {
141+
return (USBD_FAIL);
142+
}
143+
data[0] = APP_STATE_IDLE;
144+
return (USBD_OK);
145+
146+
default:
147+
return (USBD_FAIL);
148+
}
149+
}
150+
151+
#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_NUM_CONFIGURATION

cores/arduino/stm32/usb/usbd_desc.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,9 @@ __ALIGN_BEGIN uint8_t USBD_Class_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
170170
0x00, /* bcdUSB */
171171
#endif
172172
0x02,
173-
0x02, /* bDeviceClass */
174-
0x02, /* bDeviceSubClass */
175-
0x00, /* bDeviceProtocol */
173+
0xEF, /* bDeviceClass (Miscellaneous) */
174+
0x02, /* bDeviceSubClass (Common Class) */
175+
0x01, /* bDeviceProtocol (Interface Association Descriptor) */
176176
USB_MAX_EP0_SIZE, /* bMaxPacketSize */
177177
LOBYTE(USBD_VID), /* idVendor */
178178
HIBYTE(USBD_VID), /* idVendor */

0 commit comments

Comments
 (0)