2017-04-15 13:51:24 +02:00
/** library for USB DFU to write on internal flash (code)
2019-12-06 17:41:37 +01:00
* @ file
2017-04-15 13:51:24 +02:00
* @ author King Kévin < kingkevin @ cuvoodoo . info >
2020-06-06 14:35:55 +02:00
* @ copyright SPDX - License - Identifier : GPL - 3.0 - or - later
2020-01-04 14:38:22 +01:00
* @ date 2017 - 2020
2017-04-15 13:51:24 +02:00
*/
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
# include <libopencmsis/core_cm3.h> // Cortex M3 utilities
# include <libopencm3/cm3/scb.h> // reset utilities
# include <libopencm3/stm32/rcc.h> // real-time control clock library
# include <libopencm3/stm32/gpio.h> // general purpose input output library
2018-02-18 15:21:18 +01:00
# include <libopencm3/stm32/desig.h> // flash size definition
2017-04-15 13:51:24 +02:00
# 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
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 */
2019-01-12 16:22:57 +01:00
static uint32_t download_offset = 0 ; /**< destination offset of where the downloaded data should be flashed */
2020-01-04 14:38:22 +01:00
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 */
2017-04-15 13:51:24 +02:00
/** 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 */
2018-04-03 16:59:02 +02:00
. idVendor = 0x1209 , /**< pid.codes vendor ID */
2019-02-20 19:14:08 +01:00
. idProduct = 0x4256 , /**< BusVoodo product ID within the Vendor ID space */
2018-04-03 16:59:02 +02:00
. bcdDevice = 0x0000 , /**< Device Release Number: board version number */
2017-04-15 13:51:24 +02:00
. 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 */
2019-12-06 17:41:37 +01:00
. iSerialNumber = 3 , /**< the index of the string in the string table that represents the serial number of this item in string form */
2017-04-15 13:51:24 +02:00
. 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) */
2019-01-12 16:22:57 +01:00
. wDetachTimeout = 0 , /**< maximum time in milliseconds to detach (and reboot) */
2017-04-15 13:51:24 +02:00
. 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) */
2019-12-06 17:41:37 +01:00
. iInterface = 4 , /**< the index of the string in the string table that represents interface description */
2017-04-15 13:51:24 +02:00
. 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 */
} ;
2019-12-06 17:41:37 +01:00
/** device ID used as serial number */
static char usb_serial [ ] = " 00112233445566778899aabb " ;
2017-04-15 13:51:24 +02:00
/** USB string table
* @ note starts with index 1
*/
static const char * usb_dfu_strings [ ] = {
2018-04-03 16:59:02 +02:00
" CuVoodoo " , /**< manufacturer string */
2019-02-20 19:14:08 +01:00
" BusVoodoo multi-protocol debugging adapter " , /**< product string */
2019-12-06 17:41:37 +01:00
( const char * ) usb_serial , /**< device ID used as serial number */
2019-03-26 19:27:40 +01:00
" DFU bootloader (DFU mode) " , /**< DFU interface string */
2017-04-15 13:51:24 +02:00
} ;
/** disconnect USB to force re-enumerate */
static void usb_disconnect ( void )
{
2020-01-03 19:39:41 +01:00
if ( usb_device ) {
usbd_disconnect ( usb_device , true ) ;
}
2017-04-15 13:51:24 +02:00
# 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 ) ;
2020-01-03 19:41:39 +01:00
# 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 ) ;
2020-01-03 20:16:02 +01:00
gpio_clear ( GPIOB , GPIO6 ) ;
2020-01-03 19:39:41 +01:00
# endif
2017-04-15 13:51:24 +02:00
// 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 ) ;
2020-01-03 20:16:02 +01:00
// USB disconnected must be at least 10 ms long, at most 100 ms
for ( uint32_t i = 0 ; i < 0x2000 ; i + + ) {
__asm__ ( " nop " ) ;
}
2017-04-15 13:51:24 +02:00
}
2020-01-04 14:38:22 +01:00
/** disconnect USB and perform system reset
2017-04-15 13:51:24 +02:00
* @ param [ in ] usbd_dev USB device ( unused )
* @ param [ in ] req USB request ( unused )
* @ note this function is called after the corresponding GETSTATUS request
*/
2020-01-04 14:38:22 +01:00
static void usb_dfu_reset ( usbd_device * usbd_dev , struct usb_setup_data * req )
2017-04-15 13:51:24 +02:00
{
( void ) usbd_dev ; // variable not used
( void ) req ; // variable not used
2020-01-04 14:38:22 +01:00
usb_disconnect ( ) ; // USB detach (disconnect to force re-enumeration)
scb_reset_system ( ) ; // reset device
while ( true ) ; // wait for the reset to happen
2017-04-15 13:51:24 +02:00
}
2020-01-04 14:38:22 +01:00
/** flash downloaded data block
2017-04-15 13:51:24 +02:00
* @ param [ in ] usbd_dev USB device ( unused )
* @ param [ in ] req USB request ( unused )
* @ note this function is called after the corresponding GETSTATUS request
*/
2020-01-04 14:38:22 +01:00
static void usb_dfu_flash ( usbd_device * usbd_dev , struct usb_setup_data * req )
2017-04-15 13:51:24 +02:00
{
( void ) usbd_dev ; // variable not used
( void ) req ; // variable not used
2020-01-04 14:38:22 +01:00
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
2017-04-15 13:51:24 +02:00
}
/** 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
2018-04-28 12:21:32 +02:00
* @ return USBD_REQ_HANDLED if handled correctly , USBD_REQ_NOTSUPP else
2017-04-15 13:51:24 +02:00
* @ note resets device when configured with 5 bits
*/
2018-04-28 12:21:32 +02:00
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 ) )
2017-04-15 13:51:24 +02:00
{
( void ) complete ;
( void ) usbd_dev ; // device is not used
// DFU only requires handling class requests
2019-01-12 16:22:57 +01:00
if ( ( req - > bmRequestType & USB_REQ_TYPE_TYPE ) ! = USB_REQ_TYPE_CLASS ) {
2018-04-28 12:21:32 +02:00
return USBD_REQ_NOTSUPP ;
2017-04-15 13:51:24 +02:00
}
led_off ( ) ; // indicate we are processing request
2018-04-28 12:21:32 +02:00
int to_return = USBD_REQ_HANDLED ; // value to return
2017-04-15 13:51:24 +02:00
switch ( req - > bRequest ) {
case DFU_DNLOAD : // download firmware on flash
2019-01-12 16:22:57 +01:00
if ( STATE_DFU_IDLE ! = usb_dfu_state & & STATE_DFU_DNLOAD_IDLE ! = usb_dfu_state ) { // wrong start to request download
2017-04-15 13:51:24 +02:00
// warn about programming error
usb_dfu_status = DFU_STATUS_ERR_PROG ;
usb_dfu_state = STATE_DFU_ERROR ;
2019-01-12 16:22:57 +01:00
} else if ( STATE_DFU_IDLE = = usb_dfu_state & & ( ( NULL = = len ) | | ( 0 = = * len ) ) ) { // download request should not start empty
2017-04-15 13:51:24 +02:00
// warn about programming error
usb_dfu_status = DFU_STATUS_ERR_PROG ;
usb_dfu_state = STATE_DFU_ERROR ;
2019-01-12 16:22:57 +01:00
} else if ( STATE_DFU_DNLOAD_IDLE = = usb_dfu_state & & ( ( NULL = = len ) | | ( 0 = = * len ) ) ) { // download completed
2017-04-15 13:51:24 +02:00
// go to manifestation phase
usb_dfu_state = STATE_DFU_MANIFEST_SYNC ;
} else { // there is data to be flashed
2019-01-12 16:22:57 +01:00
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
2017-04-15 13:51:24 +02:00
usb_dfu_status = DFU_STATUS_ERR_PROG ;
usb_dfu_state = STATE_DFU_ERROR ;
2019-01-12 16:22:57 +01:00
} else if ( ( uint32_t ) & __application_end > = FLASH_BASE & & ( uint32_t ) & __application_beginning + download_offset + download_length > = ( uint32_t ) & __application_end ) {
2018-04-06 19:56:57 +02:00
// application data is exceeding enforced flash size for application
usb_dfu_status = DFU_STATUS_ERR_ADDRESS ;
usb_dfu_state = STATE_DFU_ERROR ;
2020-01-10 12:49:45 +01:00
} else if ( ( uint32_t ) & __application_end < FLASH_BASE & & ( uint32_t ) & __application_beginning + download_offset + download_length > = ( uint32_t ) ( FLASH_BASE + DESIG_FLASH_SIZE * 1024 ) ) {
2018-04-06 19:56:57 +02:00
// application data is exceeding advertised flash size
2017-04-15 13:51:24 +02:00
usb_dfu_status = DFU_STATUS_ERR_ADDRESS ;
usb_dfu_state = STATE_DFU_ERROR ;
} else {
// save downloaded data to be flashed
2019-01-12 16:22:57 +01:00
for ( uint16_t i = 0 ; i < * len & & i < sizeof ( download_data ) ; i + + ) {
2017-04-15 13:51:24 +02:00
download_data [ i ] = ( * buf ) [ i ] ;
}
2019-01-12 16:22:57 +01:00
if ( * len % 2 ) { // we can only write half words
download_data [ * len + + ] = 0xff ; // leave flash untouched
2019-03-26 19:27:40 +01:00
}
2020-01-04 14:38:22 +01:00
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
}
}
2017-04-15 13:51:24 +02:00
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
2018-04-28 12:21:32 +02:00
to_return = USBD_REQ_NOTSUPP ; // upload no supported
2017-04-15 13:51:24 +02:00
break ;
case DFU_GETSTATUS : // get status
( * buf ) [ 0 ] = usb_dfu_status ; // set status
2019-01-12 16:22:57 +01:00
( * buf ) [ 1 ] = 10 ; // set poll timeout (24 bits, in milliseconds) to small value for periodical poll
2017-04-15 13:51:24 +02:00
( * 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
2019-01-12 16:22:57 +01:00
if ( STATE_DFU_DNLOAD_SYNC = = usb_dfu_state ) {
2017-04-15 13:51:24 +02:00
usb_dfu_state = STATE_DFU_DNBUSY ; // switch to busy state
2019-01-12 16:22:57 +01:00
} else if ( STATE_DFU_MANIFEST_SYNC = = usb_dfu_state ) {
2020-01-04 14:38:22 +01:00
// 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
2017-04-15 13:51:24 +02:00
usb_dfu_state = STATE_DFU_MANIFEST ; // go to manifest mode
2020-01-04 14:38:22 +01:00
* complete = usb_dfu_flash ; // flash the head
2017-04-15 13:51:24 +02:00
}
break ;
case DFU_CLRSTATUS : // clear status
2019-01-12 16:22:57 +01:00
if ( STATE_DFU_ERROR = = usb_dfu_state | | DFU_STATUS_OK ! = usb_dfu_status ) { // only clear in case there is an error
2017-04-15 13:51:24 +02:00
usb_dfu_status = DFU_STATUS_OK ; // clear error status
2019-01-12 16:22:57 +01:00
usb_dfu_state = STATE_DFU_IDLE ; // put back in idle state
2017-04-15 13:51:24 +02:00
}
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
2019-01-12 16:22:57 +01:00
usb_dfu_state = STATE_DFU_IDLE ; // put back in idle state (nothing else to do)
2017-04-15 13:51:24 +02:00
break ;
2019-01-12 16:22:57 +01:00
case DFU_DETACH : // this request is only defined in runtime mode
2017-04-15 13:51:24 +02:00
default :
2018-04-28 12:21:32 +02:00
to_return = USBD_REQ_NOTSUPP ;
2017-04-15 13:51:24 +02:00
}
led_on ( ) ; // indicate we finished processing
return to_return ;
}
void usb_dfu_setup ( void )
{
2019-12-06 17:41:37 +01:00
// 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 ) {
2020-03-15 14:03:52 +01:00
id = DESIG_UNIQUE_ID2 ;
2019-12-06 17:41:37 +01:00
} else if ( i < 16 ) {
id = DESIG_UNIQUE_ID1 ;
} else {
2020-03-15 14:03:52 +01:00
id = ( DESIG_UNIQUE_ID0 < < 16 ) + ( DESIG_UNIQUE_ID0 > > 16 ) ;
2019-12-06 17:41:37 +01:00
}
// 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 ;
}
2017-04-15 13:51:24 +02:00
rcc_periph_reset_pulse ( RST_USB ) ; // reset USB peripheral
usb_disconnect ( ) ; // disconnect to force re-enumeration
2020-01-03 19:39:41 +01:00
# 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 ) ;
2020-01-03 19:41:39 +01:00
# 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 ) ;
2020-01-03 19:39:41 +01:00
# endif
2017-04-15 13:51:24 +02:00
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 ) ;
}
}