diff --git a/lib/usb_dfu.c b/lib/usb_dfu.c index 024936e..71f31da 100644 --- a/lib/usb_dfu.c +++ b/lib/usb_dfu.c @@ -1,13 +1,15 @@ -/** library for USB DFU to write on internal flash (code) +/** library for USB DFU to write on internal flash * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2017-2020 + * @note peripherals used: USB */ /* standard libraries */ #include // standard integer types #include // general utilities +#include // memory utilities /* STM32 (including CM3) libraries */ #include // Cortex M3 utilities @@ -15,14 +17,15 @@ #include // real-time control clock library #include // general purpose input output library #include // flash size definition +#include // flash utilities #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 +#include "flash_internal.h" // internal flash utilities -static uint8_t usbd_control_buffer[1024] = {0}; /**< buffer to be used for control requests (fit to flash page size) */ +static uint8_t usbd_control_buffer[1024] = {0}; /**< buffer to be used for control requests (must be a flash section divider) */ 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 */ @@ -32,6 +35,11 @@ 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 */ +/** symbol for beginning of the application + * @note this symbol will be provided by the bootloader linker script + */ +extern char __application_beginning; + /** USB DFU device descriptor * @note as defined in USB Device Firmware Upgrade specification section 4.2.1 */ @@ -111,7 +119,7 @@ static char usb_serial[] = "00112233445566778899aabb"; */ static const char *usb_dfu_strings[] = { "CuVoodoo", /**< manufacturer string */ - "CuVoodoo STM32F1xx DFU bootloader", /**< product string */ + "CuVoodoo STM32F4xx DFU bootloader", /**< product string */ (const char*)usb_serial, /**< device ID used as serial number */ "DFU bootloader (DFU mode)", /**< DFU interface string */ }; @@ -122,25 +130,13 @@ 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_mode_setup(GPIO_PORT(LED_PIN), GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN(LED_PIN)); + gpio_set_output_options(GPIO_PORT(LED_PIN), GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, GPIO_PIN(LED_PIN)); 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"); - } + for (volatile uint32_t i = 0; i < 0x8000; i++); } /** disconnect USB and perform system reset @@ -167,8 +163,47 @@ 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 + static struct flash_internal_section_info_s* erased_section = NULL; // last erased flash section + const struct flash_internal_section_info_s* target_section = flash_internal_section((uint32_t)&__application_beginning + download_offset); // get the section where to write the downloaded data + if (NULL == target_section) { // the corresponding section does not exist or is unknown + usb_dfu_status = DFU_STATUS_ERR_ADDRESS; // tell there is something wrong with the address + usb_dfu_state = STATE_DFU_ERROR; // tell the is something wrong + led_on(); // indicate we finished processing + return; + } + if (STATE_DFU_MANIFEST != usb_dfu_state && target_section != erased_section) { // we started a new section + // WARNING we except the offset to increment: if the offset lands in a previous section, it will be erased, possibly deleting previously written data + flash_clear_status_flags(); // clear all errors before performing operation + flash_unlock(); // unlock flash so we can erase the section + flash_erase_sector(target_section->number, 2); // erase section + flash_lock(); // lock back after operation completed + if (FLASH_SR) { // error during erasure happened (EOP is not set since we did not enable interrupts) + usb_dfu_status = DFU_STATUS_ERR_ERASE; // tell there is something wrong while erasing + usb_dfu_state = STATE_DFU_ERROR; // tell the is something wrong + led_on(); // indicate we finished processing + return; + } + // verify the section has been erased (recommended by TRM) + bool erased = true; + for (uint32_t* address = (uint32_t*)target_section->start; address <= (uint32_t*)target_section->end; address++) { + if (0xffffffff != *address) { + erased = false; + break; + } + } + if (!erased) { // error during erasure happened + usb_dfu_status = DFU_STATUS_ERR_ERASE; // tell there is something wrong while erasing + usb_dfu_state = STATE_DFU_ERROR; // tell the is something wrong + led_on(); // indicate we finished processing + return; + } + erased_section = (struct flash_internal_section_info_s*)target_section; // remember which section has been erased + } + flash_clear_status_flags(); // clear all errors before performing operation + flash_unlock(); // unlock flash so we can write to it + flash_program((uint32_t)&__application_beginning + download_offset, download_data, download_length); // program data + flash_lock(); // lock back after operation completed + if (0 == FLASH_SR) { // check if flashing worked (EOP is not set since we did not enable interrupts) switch (usb_dfu_state) { case STATE_DFU_DNLOAD_SYNC: // download ongoing case STATE_DFU_DNBUSY: // flashing ongoing @@ -226,28 +261,22 @@ static enum usbd_request_return_codes usb_dfu_control_request(usbd_device *usbd_ 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 + const uint32_t offset = req->wValue * sizeof(usbd_control_buffer); // remember offset + if (offset != 0 && offset <= download_offset) { // ensure the offset is incrementing (else this break our section erase procedure) 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; + } else 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)(FLASH_BASE + DESIG_FLASH_SIZE * 1024)) { + } else if ((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 - } + download_offset = offset; // remember offset + memcpy(download_data, *buf, *len); // copy data (length has already been checked)) 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 + // we will keep the beginning of the firmware and flash in 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 @@ -255,7 +284,7 @@ static enum usbd_request_return_codes usb_dfu_control_request(usbd_device *usbd_ // 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 + download_data[i] = 0xff; // invalided head (with value that still allows to program it later) } } usb_dfu_state = STATE_DFU_DNLOAD_SYNC; // go to sync state @@ -332,23 +361,15 @@ void usb_dfu_setup(void) usb_serial[i] = c; } - rcc_periph_reset_pulse(RST_USB); // reset USB peripheral + rcc_periph_reset_pulse(RST_OTGFS); // 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 + rcc_periph_clock_enable(RCC_OTGFS); // enable clock for USB domain + gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO11 | GPIO12); // set pin to alternate function + gpio_set_af(GPIOA, GPIO_AF10, GPIO11 | GPIO12); // set alternate function to USB + usb_device = usbd_init(&otgfs_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 + //usbd_register_set_config_callback(usb_device, usb_dfu_set_config); // configure the callback to setup the USB configuration endpoint } void usb_dfu_start(void) diff --git a/lib/usb_dfu.h b/lib/usb_dfu.h index 6db9b63..bee4569 100644 --- a/lib/usb_dfu.h +++ b/lib/usb_dfu.h @@ -1,8 +1,9 @@ -/** library for USB DFU to write on internal flash (API) +/** library for USB DFU to write on internal flash * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later - * @date 2017-2019 + * @date 2017-2020 + * @note peripherals used: USB */ #pragma once