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 */