/** library for USB DFU to write on internal flash (code) * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2017-2020 */ /* standard libraries */ #include // standard integer types #include // general utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities #include // reset utilities #include // real-time control clock library #include // general purpose input output library #include // flash size definition #include // USB library #include // USB DFU library #include "global.h" // global utilities #include "usb_dfu.h" // USB DFU header and definitions #include "flash_internal.h" // flash reading/writing utilities static uint8_t usbd_control_buffer[1024] = {0}; /**< buffer to be used for control requests (fit to flash page size) */ static usbd_device *usb_device = NULL; /**< structure holding all the info related to the USB device */ static enum dfu_state usb_dfu_state = STATE_DFU_IDLE; /**< current DFU state */ static enum dfu_status usb_dfu_status = DFU_STATUS_OK; /**< current DFU status */ static uint8_t download_data[sizeof(usbd_control_buffer)] = {0}; /**< downloaded data to be programmed in flash */ static uint16_t download_length = 0; /**< length of downloaded data */ static uint32_t download_offset = 0; /**< destination offset of where the downloaded data should be flashed */ static uint8_t firmware_head[8] = {0xff}; /**< to store the first bytes of the firmware, to be flashed at the end so we will stay in bootloader mode when the download is interrupted */ /** USB DFU device descriptor * @note as defined in USB Device Firmware Upgrade specification section 4.2.1 */ static const struct usb_device_descriptor usb_dfu_device = { .bLength = USB_DT_DEVICE_SIZE, /**< the size of this header in bytes, 18 */ .bDescriptorType = USB_DT_DEVICE, /**< a value of 1 indicates that this is a device descriptor */ .bcdUSB = 0x0200, /**< this device supports USB 2.0 */ .bDeviceClass = 0, /**< unused */ .bDeviceSubClass = 0, /**< unused */ .bDeviceProtocol = 0, /**< unused */ .bMaxPacketSize0 = 64, /**< packet size for endpoint zero in bytes */ .idVendor = 0x1209, /**< pid.codes vendor ID */ .idProduct = 0x4356, /**< CuVoodo product ID within the Vendor ID space */ .bcdDevice = 0x0000, /**< Device Release Number: board version number */ .iManufacturer = 1, /**< the index of the string in the string table that represents the name of the manufacturer of this device */ .iProduct = 2, /**< the index of the string in the string table that represents the name of the product */ .iSerialNumber = 3, /**< the index of the string in the string table that represents the serial number of this item in string form */ .bNumConfigurations = 1, /**< the number of possible configurations this device has */ }; /** USB DFU functional descriptor * @note as defined in USB Device Firmware Upgrade specification section 4.2.4 */ static const struct usb_dfu_descriptor usb_dfu_functional = { .bLength = sizeof(struct usb_dfu_descriptor), /**< provide own size */ .bDescriptorType = DFU_FUNCTIONAL, /**< functional descriptor type */ .bmAttributes = USB_DFU_CAN_DOWNLOAD | USB_DFU_WILL_DETACH, /**< this DFU can download and will detach after download (we don't support manifest for simplicity, technically we could) */ .wDetachTimeout = 0, /**< maximum time in milliseconds to detach (and reboot) */ .wTransferSize = sizeof(usbd_control_buffer), /**< set max transfer size */ .bcdDFUVersion = 0x0110, /**< DFU specification version 1.1 used */ }; /** USB DFU interface descriptor * @note as defined in USB Device Firmware Upgrade specification section 4.2.3 */ static const struct usb_interface_descriptor usb_dfu_interface = { .bLength = USB_DT_INTERFACE_SIZE, /**< size of descriptor in byte */ .bDescriptorType = USB_DT_INTERFACE, /**< interface descriptor type */ .bInterfaceNumber = 0, /**< this interface is the first (and only) */ .bAlternateSetting = 0, /**< no alternative settings */ .bNumEndpoints = 0, /**< only the control pipe at endpoint 0 is used */ .bInterfaceClass = 0xFE, /**< DFU interface class (not defined in libopencm3 dfu lib) */ .bInterfaceSubClass = 1, /**< DFU interface subclass (not defined in libopencm3 dfu lib) */ .bInterfaceProtocol = 2, /**< DFU interface mode protocol (not defined in libopencm3 dfu lib) */ .iInterface = 4, /**< the index of the string in the string table that represents interface description */ .extra = &usb_dfu_functional, /**< point to functional descriptor */ .extralen = sizeof(usb_dfu_functional), /**< size of functional descriptor */ }; /** USB DFU interface descriptor list */ static const struct usb_interface usb_dfu_interfaces[] = {{ .num_altsetting = 1, /**< this is the only alternative */ .altsetting = &usb_dfu_interface, /**< point to only interface descriptor */ }}; /** USB DFU configuration descriptor * @note as defined in USB Device Firmware Upgrade specification section 4.2.2 */ static const struct usb_config_descriptor usb_dfu_configuration = { .bLength = USB_DT_CONFIGURATION_SIZE, /**< the length of this header in bytes */ .bDescriptorType = USB_DT_CONFIGURATION, /**< a value of 2 indicates that this is a configuration descriptor */ .wTotalLength = 0, /**< total size of the configuration descriptor including all sub interfaces (automatically filled in by the USB stack in libopencm3) */ .bNumInterfaces = LENGTH(usb_dfu_interfaces), /**< the number of interfaces in this configuration */ .bConfigurationValue = 1, /**< the index of this configuration */ .iConfiguration = 0, /**< a string index describing this configuration (zero means not provided) */ .bmAttributes = 0x80, /**< bus powered (1<<7) */ .bMaxPower = 0x32, /**< the maximum amount of current that this device will draw in 2mA units */ // end of header .interface = usb_dfu_interfaces, /**< pointer to an array of interfaces */ }; /** device ID used as serial number */ static char usb_serial[] = "00112233445566778899aabb"; /** USB string table * @note starts with index 1 */ static const char *usb_dfu_strings[] = { "CuVoodoo", /**< manufacturer string */ "CuVoodoo STM32F1xx DFU bootloader", /**< product string */ (const char*)usb_serial, /**< device ID used as serial number */ "DFU bootloader (DFU mode)", /**< DFU interface string */ }; /** disconnect USB to force re-enumerate */ static void usb_disconnect(void) { if (usb_device) { usbd_disconnect(usb_device, true); } #if defined(MAPLE_MINI) // disconnect USB D+ using dedicated DISC line/circuit on PB9 rcc_periph_clock_enable(RCC_GPIOB); gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO9); gpio_set(GPIOB, GPIO9); #elif defined(BLASTER) // enable USB D+ pull-up rcc_periph_clock_enable(RCC_GPIOB); gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6); gpio_clear(GPIOB, GPIO6); #endif // pull USB D+ low for a short while rcc_periph_clock_enable(RCC_GPIOA); gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO12); gpio_clear(GPIOA, GPIO12); // USB disconnected must be at least 10 ms long, at most 100 ms for (uint32_t i = 0; i < 0x2000; i++) { __asm__("nop"); } } /** disconnect USB and perform system reset * @param[in] usbd_dev USB device (unused) * @param[in] req USB request (unused) * @note this function is called after the corresponding GETSTATUS request */ static void usb_dfu_reset(usbd_device *usbd_dev, struct usb_setup_data *req) { (void)usbd_dev; // variable not used (void)req; // variable not used usb_disconnect(); // USB detach (disconnect to force re-enumeration) scb_reset_system(); // reset device while (true); // wait for the reset to happen } /** flash downloaded data block * @param[in] usbd_dev USB device (unused) * @param[in] req USB request (unused) * @note this function is called after the corresponding GETSTATUS request */ static void usb_dfu_flash(usbd_device *usbd_dev, struct usb_setup_data *req) { (void)usbd_dev; // variable not used (void)req; // variable not used led_off(); // indicate we are processing int32_t rc = flash_internal_write((uint32_t)&__application_beginning + download_offset, download_data, download_length, true); // write downloaded data if (rc >= download_length) { // check if flashing worked switch (usb_dfu_state) { case STATE_DFU_DNLOAD_SYNC: // download ongoing case STATE_DFU_DNBUSY: // flashing ongoing usb_dfu_state = STATE_DFU_DNLOAD_IDLE; // go back to idle stat to wait for next segment break; case STATE_DFU_MANIFEST: // this was the last part led_off(); // indicate the end usb_dfu_reset(NULL, NULL); // start reset without waiting for request since we advertised we would detach break; default: // unknown state usb_dfu_status = DFU_STATUS_ERR_PROG; usb_dfu_state = STATE_DFU_ERROR; break; } } else { // warn about writing error usb_dfu_status = DFU_STATUS_ERR_WRITE; usb_dfu_state = STATE_DFU_ERROR; } led_on(); // indicate we finished processing } /** handle incoming USB DFU control request * @param[in] usbd_dev USB device descriptor * @param[in] req control request information * @param[in] buf control request data * @param[in] len control request data length * @param[in] complete not used * @return USBD_REQ_HANDLED if handled correctly, USBD_REQ_NOTSUPP else * @note resets device when configured with 5 bits */ static enum usbd_request_return_codes usb_dfu_control_request(usbd_device *usbd_dev, struct usb_setup_data *req, uint8_t **buf, uint16_t *len, void (**complete)(usbd_device *usbd_dev, struct usb_setup_data *req)) { (void)complete; (void)usbd_dev; // device is not used // DFU only requires handling class requests if ((req->bmRequestType & USB_REQ_TYPE_TYPE) != USB_REQ_TYPE_CLASS) { return USBD_REQ_NOTSUPP; } led_off(); // indicate we are processing request int to_return = USBD_REQ_HANDLED; // value to return switch (req->bRequest) { case DFU_DNLOAD: // download firmware on flash if (STATE_DFU_IDLE != usb_dfu_state && STATE_DFU_DNLOAD_IDLE != usb_dfu_state) { // wrong start to request download // warn about programming error usb_dfu_status = DFU_STATUS_ERR_PROG; usb_dfu_state = STATE_DFU_ERROR; } else if (STATE_DFU_IDLE == usb_dfu_state && ((NULL == len) || (0 == *len))) { // download request should not start empty // warn about programming error usb_dfu_status = DFU_STATUS_ERR_PROG; usb_dfu_state = STATE_DFU_ERROR; } else if (STATE_DFU_DNLOAD_IDLE == usb_dfu_state && ((NULL == len) || (0 == *len))) { // download completed // go to manifestation phase usb_dfu_state = STATE_DFU_MANIFEST_SYNC; } else { // there is data to be flashed download_length = *len; // remember download length download_offset = req->wValue * sizeof(usbd_control_buffer); // remember offset if (*len > sizeof(usbd_control_buffer) || *len > sizeof(download_data)) { // got too much data usb_dfu_status = DFU_STATUS_ERR_PROG; usb_dfu_state = STATE_DFU_ERROR; } else if ((uint32_t)&__application_end >= FLASH_BASE && (uint32_t)&__application_beginning + download_offset + download_length >= (uint32_t)&__application_end) { // application data is exceeding enforced flash size for application usb_dfu_status = DFU_STATUS_ERR_ADDRESS; usb_dfu_state = STATE_DFU_ERROR; } else if ((uint32_t)&__application_end < FLASH_BASE && (uint32_t)&__application_beginning + download_offset + download_length >= (uint32_t)(FLASH_BASE + desig_get_flash_size() * 1024)) { // application data is exceeding advertised flash size usb_dfu_status = DFU_STATUS_ERR_ADDRESS; usb_dfu_state = STATE_DFU_ERROR; } else { // save downloaded data to be flashed for (uint16_t i = 0 ; i < *len && i < sizeof(download_data); i++) { download_data[i] = (*buf)[i]; } if (*len % 2) { // we can only write half words download_data[*len++] = 0xff; // leave flash untouched } if (0 == download_offset) { // this is the beginning of the firmware // we will keep the beginning of the firmware and flash is at the head // this is because the head is used to check if the bootloader should boot into the application // but if the download gets interrupted, the head is valid and the application will be started // but the application is corrupted, and will not allow to go back to the bootloader // by flashing the firmware head at the end, we ensure the application to boot is valid // note: the force DFU input/button could be used to start the bootloader when the application is corrupted for (uint16_t i = 0 ; i < LENGTH(firmware_head) && i < sizeof(download_data); i++) { firmware_head[i] = download_data[i]; // backup head download_data[i] = 0xff; // invalided head, but will value which is quick to flash } } usb_dfu_state = STATE_DFU_DNLOAD_SYNC; // go to sync state *complete = usb_dfu_flash; // start flashing the downloaded data } } break; case DFU_UPLOAD: // upload firmware from flash to_return = USBD_REQ_NOTSUPP; // upload no supported break; case DFU_GETSTATUS: // get status (*buf)[0] = usb_dfu_status; // set status (*buf)[1] = 10; // set poll timeout (24 bits, in milliseconds) to small value for periodical poll (*buf)[2] = 0; // set poll timeout (24 bits, in milliseconds) to small value for periodical poll (*buf)[3] = 0; // set poll timeout (24 bits, in milliseconds) to small value for periodical poll (*buf)[4] = usb_dfu_state; // set state (*buf)[5] = 0; // string not used *len = 6; // set length of buffer to return if (STATE_DFU_DNLOAD_SYNC == usb_dfu_state) { usb_dfu_state = STATE_DFU_DNBUSY; // switch to busy state } else if (STATE_DFU_MANIFEST_SYNC == usb_dfu_state) { // we still need to flash the saved head of the firmware for (uint16_t i = 0 ; i < LENGTH(firmware_head) && i < sizeof(download_data); i++) { download_data[i] = firmware_head[i]; // prepare data to be flashed } download_offset = 0; // flash the head of the firmware download_length = sizeof(firmware_head); // only flash the head usb_dfu_state = STATE_DFU_MANIFEST; // go to manifest mode *complete = usb_dfu_flash; // flash the head } break; case DFU_CLRSTATUS: // clear status if (STATE_DFU_ERROR == usb_dfu_state || DFU_STATUS_OK != usb_dfu_status) { // only clear in case there is an error usb_dfu_status = DFU_STATUS_OK; // clear error status usb_dfu_state = STATE_DFU_IDLE; // put back in idle state } break; case DFU_GETSTATE: // get state (*buf)[0] = usb_dfu_state; // return state *len = 1; // only state needs to be provided break; case DFU_ABORT: // abort current operation usb_dfu_state = STATE_DFU_IDLE; // put back in idle state (nothing else to do) break; case DFU_DETACH: // this request is only defined in runtime mode default: to_return = USBD_REQ_NOTSUPP; } led_on(); // indicate we finished processing return to_return; } void usb_dfu_setup(void) { // set serial for (uint8_t i = 0; i < LENGTH(usb_serial) - 1; i++) { // write the serial uint32_t id; // current ID part if (i < 8) { id = DESIG_UNIQUE_ID2; } else if (i < 16) { id = DESIG_UNIQUE_ID1; } else { id = (DESIG_UNIQUE_ID0 << 16) + (DESIG_UNIQUE_ID0 >> 16); } // transform into character char c = (id >> ((7 - (i % 8)) * 4)) & 0x0f; // get nibble if (c < 10) { c = '0' + c; } else { c = 'a' + (c - 10); } // set character usb_serial[i] = c; } rcc_periph_reset_pulse(RST_USB); // reset USB peripheral usb_disconnect(); // disconnect to force re-enumeration #if defined(MAPLE_MINI) // connect USB D+ using dedicated DISC line/circuit on PB9 rcc_periph_clock_enable(RCC_GPIOB); gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO9); gpio_clear(GPIOB, GPIO9); #elif defined(BLASTER) // enable USB D+ pull-up using GPIO rcc_periph_clock_enable(RCC_GPIOB); gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6); gpio_set(GPIOB, GPIO6); #endif rcc_periph_clock_enable(RCC_GPIOA); // enable clock for GPIO used for USB rcc_periph_clock_enable(RCC_USB); // enable clock for USB domain usb_device = usbd_init(&st_usbfs_v1_usb_driver, &usb_dfu_device, &usb_dfu_configuration, usb_dfu_strings, LENGTH(usb_dfu_strings), usbd_control_buffer, sizeof(usbd_control_buffer)); // configure USB device usbd_register_control_callback(usb_device, USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, usb_dfu_control_request); // set control request handling DFU operations } void usb_dfu_start(void) { // infinitely poll device to handle requests while (true) { usbd_poll(usb_device); } }