diff --git a/boards.txt b/boards.txt index 65074d3a9f..b82635c93a 100644 --- a/boards.txt +++ b/boards.txt @@ -181,6 +181,7 @@ Nucleo_144.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Nucleo_144.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Nucleo_144.menu.upload_method.dfuMethod.upload.protocol=2 Nucleo_144.menu.upload_method.dfuMethod.upload.options=-g +Nucleo_144.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Nucleo_144.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -466,6 +467,7 @@ Nucleo_64.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Nucleo_64.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Nucleo_64.menu.upload_method.dfuMethod.upload.protocol=2 Nucleo_64.menu.upload_method.dfuMethod.upload.options=-g +Nucleo_64.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Nucleo_64.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -571,6 +573,7 @@ Nucleo_32.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Nucleo_32.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Nucleo_32.menu.upload_method.dfuMethod.upload.protocol=2 Nucleo_32.menu.upload_method.dfuMethod.upload.options=-g +Nucleo_32.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Nucleo_32.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -727,6 +730,7 @@ Disco.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Disco.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Disco.menu.upload_method.dfuMethod.upload.protocol=2 Disco.menu.upload_method.dfuMethod.upload.options=-g +Disco.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Disco.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -759,6 +763,7 @@ Eval.menu.upload_method.swdMethod.upload.tool=stm32CubeProg Eval.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Eval.menu.upload_method.dfuMethod.upload.protocol=2 Eval.menu.upload_method.dfuMethod.upload.options=-g +Eval.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Eval.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -847,6 +852,7 @@ GenF0.menu.upload_method.serialMethod.upload.tool=stm32CubeProg GenF0.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) GenF0.menu.upload_method.dfuMethod.upload.protocol=2 GenF0.menu.upload_method.dfuMethod.upload.options=-g +GenF0.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true GenF0.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -1152,6 +1158,7 @@ GenF1.menu.upload_method.serialMethod.upload.tool=stm32CubeProg GenF1.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) GenF1.menu.upload_method.dfuMethod.upload.protocol=2 GenF1.menu.upload_method.dfuMethod.upload.options=-g +GenF1.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true GenF1.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg GenF1.menu.upload_method.bmpMethod=BMP (Black Magic Probe) @@ -1215,6 +1222,7 @@ GenF3.menu.upload_method.serialMethod.upload.tool=stm32CubeProg GenF3.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) GenF3.menu.upload_method.dfuMethod.upload.protocol=2 GenF3.menu.upload_method.dfuMethod.upload.options=-g +GenF3.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true GenF3.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg GenF3.menu.upload_method.bmpMethod=BMP (Black Magic Probe) @@ -1623,6 +1631,7 @@ GenF4.menu.upload_method.serialMethod.upload.tool=stm32CubeProg GenF4.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) GenF4.menu.upload_method.dfuMethod.upload.protocol=2 GenF4.menu.upload_method.dfuMethod.upload.options=-g +GenF4.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true GenF4.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg GenF4.menu.upload_method.bmpMethod=BMP (Black Magic Probe) @@ -1712,6 +1721,7 @@ GenL0.menu.upload_method.serialMethod.upload.tool=stm32CubeProg GenL0.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) GenL0.menu.upload_method.dfuMethod.upload.protocol=2 GenL0.menu.upload_method.dfuMethod.upload.options=-g +GenL0.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true GenL0.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg GenL0.menu.upload_method.bmpMethod=BMP (Black Magic Probe) @@ -1752,6 +1762,7 @@ ESC_board.menu.upload_method.serialMethod.upload.tool=stm32CubeProg ESC_board.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) ESC_board.menu.upload_method.dfuMethod.upload.protocol=2 ESC_board.menu.upload_method.dfuMethod.upload.options=-g +ESC_board.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true ESC_board.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ @@ -1834,6 +1845,7 @@ LoRa.menu.upload_method.serialMethod.upload.tool=stm32CubeProg LoRa.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) LoRa.menu.upload_method.dfuMethod.upload.protocol=2 LoRa.menu.upload_method.dfuMethod.upload.options=-g +LoRa.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true LoRa.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ############################### @@ -2010,6 +2022,7 @@ LoRa.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg 3dprinter.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) 3dprinter.menu.upload_method.dfuMethod.upload.protocol=2 3dprinter.menu.upload_method.dfuMethod.upload.options=-g +3dprinter.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true 3dprinter.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg @@ -2069,6 +2082,7 @@ Genericflight.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Genericflight.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Genericflight.menu.upload_method.dfuMethod.upload.protocol=2 Genericflight.menu.upload_method.dfuMethod.upload.options=-g +Genericflight.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Genericflight.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg Genericflight.menu.upload_method.bmpMethod=BMP (Black Magic Probe) @@ -2199,6 +2213,7 @@ Midatronics.menu.upload_method.serialMethod.upload.tool=stm32CubeProg Midatronics.menu.upload_method.dfuMethod=STM32CubeProgrammer (DFU) Midatronics.menu.upload_method.dfuMethod.upload.protocol=2 Midatronics.menu.upload_method.dfuMethod.upload.options=-g +Midatronics.menu.upload_method.dfuMethod.upload.use_1200bps_touch=true Midatronics.menu.upload_method.dfuMethod.upload.tool=stm32CubeProg ################################################################################ diff --git a/cores/arduino/stm32/bootloader.h b/cores/arduino/stm32/bootloader.h index 118bc7b860..cb077a624a 100644 --- a/cores/arduino/stm32/bootloader.h +++ b/cores/arduino/stm32/bootloader.h @@ -1,6 +1,8 @@ #ifndef _BOOTLOADER_H_ #define _BOOTLOADER_H_ +#include + /* Ensure DTR_TOGGLING_SEQ enabled */ #if defined(BL_LEGACY_LEAF) || defined(BL_HID) #ifndef DTR_TOGGLING_SEQ @@ -12,6 +14,21 @@ extern "C" { #endif /* __cplusplus */ +void scheduleBootloaderReset(); +void cancelBootloaderReset(); +void bootloaderSystickHandler(); + +/* Request to jump to system memory boot */ +void jumpToBootloaderRequested(void); + +/* Jump to system memory boot from user application */ +void jumpToBootloaderIfRequested(void); + +#ifdef DTR_TOGGLING_SEQ +/* DTR toggling sequence management */ +void dtr_togglingHook(uint8_t *buf, uint32_t *len); +#endif + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/cores/arduino/stm32/startup_stm32yyxx.S b/cores/arduino/stm32/startup_stm32yyxx.S index 8572083cdd..088f6ccabc 100644 --- a/cores/arduino/stm32/startup_stm32yyxx.S +++ b/cores/arduino/stm32/startup_stm32yyxx.S @@ -3,3 +3,8 @@ #if defined(CMSIS_STARTUP_FILE) #include CMSIS_STARTUP_FILE #endif + +# Expose Reset_Handler under a different name, to allow overriding it +# with a strong symbol and then calling the original. +.global Original_Reset_Handler +.thumb_set Original_Reset_Handler,Reset_Handler 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/cdc/usbd_cdc_if.c b/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c index c09675f95e..b340a6b553 100644 --- a/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c +++ b/cores/arduino/stm32/usb/cdc/usbd_cdc_if.c @@ -33,6 +33,9 @@ #define CDC_MAX_PACKET_SIZE USB_MAX_EP0_SIZE #endif +// TODO: Put this elsewhere +#define BOOTLOADER_RESET_1200_BAUD + /* * The value USB_CDC_TRANSMIT_TIMEOUT is defined in terms of HAL_GetTick() units. * Typically it is 1ms value. The timeout determines when we would consider the @@ -56,8 +59,7 @@ __IO bool receivePended = true; static uint32_t transmitStart = 0; #ifdef DTR_TOGGLING_SEQ - /* DTR toggling sequence management */ - extern void dtr_togglingHook(uint8_t *buf, uint32_t *len); + uint8_t dtr_toggling = 0; #endif @@ -201,6 +203,19 @@ static int8_t USBD_CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length) break; } +#ifdef BOOTLOADER_RESET_1200_BAUD + if (cmd == CDC_SET_LINE_CODING || cmd == CDC_SET_CONTROL_LINE_STATE) { + // Auto-reset into the bootloader is triggered when the port, already + // open at 1200 bps, is closed. Cancel the reset when the port is + // opened again. + if (linecoding.bitrate == 1200 && !lineState) { + scheduleBootloaderReset(); + } else { + cancelBootloaderReset(); + } + } +#endif /* BOOTLOADER_RESET_1200_BAUD */ + return ((int8_t)USBD_OK); } 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 */ diff --git a/cores/arduino/stm32/usb/usbd_if.c b/cores/arduino/stm32/usb/usbd_if.c index e579140f72..8a8c60301a 100644 --- a/cores/arduino/stm32/usb/usbd_if.c +++ b/cores/arduino/stm32/usb/usbd_if.c @@ -115,6 +115,56 @@ #define USBD_DP_TRICK #endif +/** + * @brief USBD_early_startup_delay + * @param us - number of us to delay + * @retval None + * + * This is a minimal delay which is usable in very early startup, when + * nothing has been initialized yet (no clocks, no memory, no systick + * timer). It works by counting CPU cycles, and assumes the system is + * still running from the HSI. + * + * If the systick timer is already enabled, this assumes everything is + * intialized and instead used the normal delayMicroseconds function. + * + * Max delay depends on HSI, but is around 268 sec with 16Mhz HSI. + */ +void USBD_early_startup_delay_us(uint32_t us) +{ + if (SysTick->CTRL & SysTick_CTRL_ENABLE_Msk) { + delayMicroseconds(us); + return; + } + +#if !HSI_VALUE +#error "Missing HSI_VALUE" +#endif + +#if HSI_VALUE % 4000000 != 0 +#warning "HSI not multiple of 4MHz, early startup delay will be inaccurate!" +#endif + + // To simplify this calculation, this assumes the HSI runs at a + // multiple of 4Mhz (1Mhz to scale to us, times 4 to account for 4 + // cycles per loop). + const uint32_t loops_per_us = (HSI_VALUE / 1000000) / 4; + const uint32_t loop_count = us * loops_per_us; + + // Assembly loop, designed to run at exactly 4 cycles per loop. + asm volatile( + // Use the unified ARM/Thumb syntax, which seems to be more + // universally used and corresponds to what avr-objdump outputs + // See https://sourceware.org/binutils/docs/as/ARM_002dInstruction_002dSet.html + ".syntax unified\n\t" + "1:\n\t" + "nop /* 1 cycle */\n\t" + "subs %[loop], %[loop], #1 /* 1 cycle */\n\t" + "bne 1b /* 2 if taken, 1 otherwise */\n\t" + : : [loop] "l"(loop_count) + ); +} + /** * @brief Force to re-enumerate USB. * @@ -134,7 +184,7 @@ WEAK void USBD_reenumerate(void) digitalWriteFast(USBD_PULLUP_CONTROL_PINNAME, USBD_DETACH_LEVEL); /* Wait */ - delay(USBD_ENUM_DELAY); + USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000); /* Attach */ #if defined(USBD_DP_TRICK) @@ -144,9 +194,18 @@ WEAK void USBD_reenumerate(void) digitalWriteFast(USBD_PULLUP_CONTROL_PINNAME, USBD_ATTACH_LEVEL); #endif /* defined(USBD_PULLUP_CONTROL_FLOATING) */ #elif defined(USBD_HAVE_INTERNAL_PULLUPS) - USB_DevDisconnect(USBD_USB_INSTANCE); - delay(USBD_ENUM_DELAY); - USB_DevConnect(USBD_USB_INSTANCE); +#ifdef USB_OTG_DCTL_SDIS + uint32_t USBx_BASE = (uint32_t)USBD_USB_INSTANCE; + USBx_DEVICE->DCTL |= USB_OTG_DCTL_SDIS; + //USB_DevDisconnect(USBD_USB_INSTANCE); + USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000); + //USB_DevConnect(USBD_USB_INSTANCE); + USBx_DEVICE->DCTL &= ~USB_OTG_DCTL_SDIS; +#else + USBD_USB_INSTANCE->BCDR &= (uint16_t)(~(USB_BCDR_DPPU)); + USBD_early_startup_delay_us(USBD_ENUM_DELAY * 1000); + USBD_USB_INSTANCE->BCDR |= (uint16_t)(USB_BCDR_DPPU); +#endif #else #warning "No USB attach/detach method, USB might not be reliable through system resets" #endif diff --git a/libraries/SrcWrapper/src/stm32/bootloader.c b/libraries/SrcWrapper/src/stm32/bootloader.c index d7d86e968e..f75503f4bf 100644 --- a/libraries/SrcWrapper/src/stm32/bootloader.c +++ b/libraries/SrcWrapper/src/stm32/bootloader.c @@ -1,8 +1,156 @@ +#include + #include "bootloader.h" #include "stm32_def.h" #include "backup.h" +#include "stm32yyxx_ll_system.h" +#include "usbd_if.h" + +/* + * STM32 built-in bootloader in system memory support + */ +#if !defined(STM32MP1xx) +static const uint32_t BOOTLOADER_DELAY_MS = 250; +static bool BootIntoBootloaderAfterReset; +static uint32_t countdown = 0; + + +/* Request to jump to system memory boot */ +WEAK void jumpToBootloaderRequested(void) +{ + BootIntoBootloaderAfterReset = true; + NVIC_SystemReset(); +} + +// Defined in startup assembly code +void Original_Reset_Handler(); + +// This overrides the Reset_Handler that is run on reset before +// *anything* else (including memory initialization). Only the stack +// pointer is set up by this time. +void Reset_Handler() +{ + // Jump to the bootloader if needed. + jumpToBootloaderIfRequested(); + + // Continue with regular startup by calling the original reset handler + Original_Reset_Handler(); +} + +/* Figure out where the bootloader lives, remapping memory if needed, + * and return its address. The returned address should point to the + * bootloader's interrupt vector table, so to a SP to load followed by + * an address to jump to. + */ +WEAK uint32_t bootloaderAddress() +{ +#ifdef __HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH + /* Remap system Flash memory at address 0x00000000 */ + __HAL_SYSCFG_REMAPMEMORY_SYSTEMFLASH(); + // Make the variable volatile to prevent the compiler from seeing a + // null-pointer dereference (which is undefined in C) and generating + // an UDF (undefined) instruction instead of just loading address 0. + return 0; +#elif defined(STM32F1xx) && (defined (STM32F101xG) || defined (STM32F103xG)) + // From AN2606, table 136 "Bootloader device-dependent parameters" + // STM32F10xxx XL-density, aka 768K-1M flash, aka F and G flash size codes + return 0x1FFFE000; +#elif defined(STM32F1xx) && defined (STM32F105xC) || defined (STM32F107xC) + // STM32F105xx/107xx from AN2606, table 136 "Bootloader device-dependent parameters" + return 0x1FFFB000; +#elif defined (STM32F100xB) || defined (STM32F100xE) || defined (STM32F101x6) || \ + defined (STM32F101xB) || defined (STM32F101xE) || defined (STM32F102x6) || \ + defined (STM32F102xB) || defined (STM32F103x6) || defined (STM32F103xB) || \ + defined (STM32F103xE) + // STM32F10xxx from AN2606, table 136 "Bootloader device-dependent parameters" + // This does not check for STM32F1xx, to prevent catching + // STM32F105xx/STM32F107xx or XL-density chips that are introduced later. + // Defines from system/Drivers/CMSIS/Device/ST/STM32F1xx/Include/stm32f1xx.h + return 0x1FFFF000; +#elif defined(STM32F7xx) || defined(STM32H7xx) + // From AN2606, table 136 "Bootloader device-dependent parameters" + // TODO: Reference manual for has a different value... + return 0x1FF00000; +#else +#error "System flash address unknown for this CPU" +#endif +} + + +/* Jump to system memory boot from user application */ +WEAK void jumpToBootloaderIfRequested(void) +{ + // Boot into bootloader if BootIntoBootloaderAfterReset is set. + // Note that BootIntoBootloaderAfterReset is a noinit variable, so it + // s not automatically initialized on startup (so it can keep its + // value across resets). At initial poweron, its value can be + // *anything*, so only consider its value after a software reset. In + // all cases, clear its value (this both takes care of giving it an + // initial value after power-up, and prevents booting into the + // bootloader more than once for a single request). + bool doBootloader = __HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) && BootIntoBootloaderAfterReset; + BootIntoBootloaderAfterReset = false; + + if (doBootloader) { + __HAL_RCC_CLEAR_RESET_FLAGS(); + +#ifdef USBCON + USBD_reenumerate(); +#endif + + uint32_t sys = bootloaderAddress(); + + // This is assembly to prevent modifying the stack pointer after + // loading it, and to ensure a jump (not call) to the bootloader. + // Not sure if the barriers are really needed, they were taken from + // https://github.com/GrumpyOldPizza/arduino-STM32L4/blob/ac659033eadd50cfe001ba1590a1362b2d87bb76/system/STM32L4xx/Source/boot_stm32l4xx.c#L159-L165 + asm volatile( + "ldr r0, [%[sys], #0] \n\t" // get address of stack pointer + "msr msp, r0 \n\t" // set stack pointer + "ldr r0, [%[sys], #4] \n\t" // get address of reset handler + "dsb \n\t" // data sync barrier + "isb \n\t" // instruction sync barrier + "bx r0 \n\t" // branch to bootloader + : : [sys] "l"(sys) : "r0" + ); + + __builtin_unreachable(); + } +} + +/** + * Scheduler a reset into the bootloader after a delay. + */ +void scheduleBootloaderReset() +{ + countdown = BOOTLOADER_DELAY_MS; +} + +/** + * Cancel a previously scheduled bootloader reset. + */ +void cancelBootloaderReset() +{ + countdown = 0; +} +#endif /* !STM32MP1xx */ + +/** + * Bootloader systick handler, should be called every ms + */ +void bootloaderSystickHandler() +{ +#ifndef STM32MP1xx + if (countdown && --countdown == 0) { + jumpToBootloaderRequested(); + } +#endif /* !STM32MP1xx */ +} +/* + * Legacy maple bootloader support + */ #ifdef BL_LEGACY_LEAF void dtr_togglingHook(uint8_t *buf, uint32_t *len) { @@ -17,6 +165,9 @@ void dtr_togglingHook(uint8_t *buf, uint32_t *len) } #endif /* BL_LEGACY_LEAF */ +/* + * HID bootloader support + */ #ifdef BL_HID void dtr_togglingHook(uint8_t *buf, uint32_t *len) { diff --git a/libraries/SrcWrapper/src/stm32/clock.c b/libraries/SrcWrapper/src/stm32/clock.c index bea7adcdbc..4c65c98826 100644 --- a/libraries/SrcWrapper/src/stm32/clock.c +++ b/libraries/SrcWrapper/src/stm32/clock.c @@ -37,6 +37,7 @@ */ #include "backup.h" #include "clock.h" +#include "bootloader.h" #include "stm32yyxx_ll_cortex.h" #ifdef __cplusplus @@ -88,6 +89,7 @@ void SysTick_Handler(void) HAL_IncTick(); HAL_SYSTICK_IRQHandler(); osSystickHandler(); + bootloaderSystickHandler(); } /** diff --git a/libraries/SrcWrapper/src/stm32/hw_config.c b/libraries/SrcWrapper/src/stm32/hw_config.c index 3434d6da96..20013b2aa5 100644 --- a/libraries/SrcWrapper/src/stm32/hw_config.c +++ b/libraries/SrcWrapper/src/stm32/hw_config.c @@ -35,10 +35,11 @@ * ****************************************************************************** */ -#include "stm32_def.h" +#include "bootloader.h" +#include "dwt.h" #include "hw_config.h" #include "usbd_if.h" -#include "dwt.h" +#include "stm32_def.h" #ifdef __cplusplus extern "C" {