usb_dfu: update to work with F4
This commit is contained in:
parent
46083bdf5e
commit
9fbf5b4aad
121
lib/usb_dfu.c
121
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 <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2017-2020
|
||||
* @note peripherals used: USB
|
||||
*/
|
||||
|
||||
/* standard libraries */
|
||||
#include <stdint.h> // standard integer types
|
||||
#include <stdlib.h> // general utilities
|
||||
#include <string.h> // memory utilities
|
||||
|
||||
/* STM32 (including CM3) libraries */
|
||||
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
|
||||
@ -15,14 +17,15 @@
|
||||
#include <libopencm3/stm32/rcc.h> // real-time control clock library
|
||||
#include <libopencm3/stm32/gpio.h> // general purpose input output library
|
||||
#include <libopencm3/stm32/desig.h> // flash size definition
|
||||
#include <libopencm3/stm32/flash.h> // flash utilities
|
||||
#include <libopencm3/usb/usbd.h> // USB library
|
||||
#include <libopencm3/usb/dfu.h> // 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)
|
||||
|
@ -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 <kingkevin@cuvoodoo.info>
|
||||
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
|
||||
* @date 2017-2019
|
||||
* @date 2017-2020
|
||||
* @note peripherals used: USB
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user