From 1173c3d0a850d7b94aff452848c602606d267f61 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Wed, 22 Jan 2020 14:38:58 +0100 Subject: [PATCH] [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 https://github.com/stm32duino/Arduino_Core_STM32/issues/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. --- cores/arduino/stm32/usb/cdc/usbd_cdc.c | 81 +++++++++++-- cores/arduino/stm32/usb/cdc/usbd_cdc.h | 3 +- cores/arduino/stm32/usb/dfu_runtime.h | 151 +++++++++++++++++++++++++ cores/arduino/stm32/usb/usbd_conf.h | 2 +- cores/arduino/stm32/usb/usbd_desc.c | 6 +- 5 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 cores/arduino/stm32/usb/dfu_runtime.h diff --git a/cores/arduino/stm32/usb/cdc/usbd_cdc.c b/cores/arduino/stm32/usb/cdc/usbd_cdc.c index 9ee0d7885a..86185360d6 100644 --- a/cores/arduino/stm32/usb/cdc/usbd_cdc.c +++ b/cores/arduino/stm32/usb/cdc/usbd_cdc.c @@ -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 */ @@ -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, }; @@ -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 */ @@ -318,7 +347,9 @@ __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 = { @@ -326,11 +357,26 @@ __ALIGN_BEGIN static uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] 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 */ @@ -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, }; /** @@ -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, diff --git a/cores/arduino/stm32/usb/cdc/usbd_cdc.h b/cores/arduino/stm32/usb/cdc/usbd_cdc.h index 5395708c06..fd4a8022ae 100644 --- a/cores/arduino/stm32/usb/cdc/usbd_cdc.h +++ b/cores/arduino/stm32/usb/cdc/usbd_cdc.h @@ -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 * @{ @@ -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 diff --git a/cores/arduino/stm32/usb/dfu_runtime.h b/cores/arduino/stm32/usb/dfu_runtime.h new file mode 100644 index 0000000000..052d65e5ae --- /dev/null +++ b/cores/arduino/stm32/usb/dfu_runtime.h @@ -0,0 +1,151 @@ +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __USB_DFU_RUNTIME_H +#define __USB_DFU_RUNTIME_H + +#include + +/**************************************************/ +/* 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 diff --git a/cores/arduino/stm32/usb/usbd_conf.h b/cores/arduino/stm32/usb/usbd_conf.h index c1a4808380..d0c9a7c8f5 100644 --- a/cores/arduino/stm32/usb/usbd_conf.h +++ b/cores/arduino/stm32/usb/usbd_conf.h @@ -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 diff --git a/cores/arduino/stm32/usb/usbd_desc.c b/cores/arduino/stm32/usb/usbd_desc.c index 229112ea5f..6c92dc256c 100644 --- a/cores/arduino/stm32/usb/usbd_desc.c +++ b/cores/arduino/stm32/usb/usbd_desc.c @@ -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 */