2020-03-08 18:17:37 +01:00
/** library to read/write internal flash
2020-01-01 23:35:32 +01:00
* @ file
2016-08-14 19:25:38 +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-01 23:35:32 +01:00
* @ date 2016 - 2020
2016-08-14 19:25:38 +02:00
* @ note peripherals used : none
*/
/* standard libraries */
# include <stdint.h> // standard integer types
# include <stdlib.h> // general utilities
/* STM32 (including CM3) libraries */
# include <libopencm3/stm32/flash.h> // flash utilities
2018-02-18 15:21:18 +01:00
# include <libopencm3/stm32/desig.h> // device signature definitions
# include <libopencm3/stm32/dbgmcu.h> // debug definitions
2016-08-14 19:25:38 +02:00
2020-01-01 23:35:32 +01:00
/* own libraries */
2016-08-14 19:25:38 +02:00
# include "flash_internal.h" // flash storage library API
# include "global.h" // global definitions
2020-02-19 20:58:32 +01:00
/** flash page size */
static uint16_t flash_internal_page = 0 ;
/** end address of flash */
static uint32_t flash_internal_end = 0 ;
2020-01-02 18:26:12 +01:00
/** start address of flash memory used for the emulated EEPROM */
static uint32_t flash_internal_eeprom_start = 0 ;
/** start address of emulated EEPROM */
static uint32_t flash_internal_eeprom_address = 0 ;
2020-02-19 20:58:32 +01:00
/** find out page size and flash end address */
static void flash_internal_init ( void )
{
if ( 0 = = flash_internal_page ) {
flash_internal_page_size ( ) ; // get page size
}
if ( 0 = = flash_internal_end ) {
if ( ( uint32_t ) & __flash_end > = FLASH_BASE ) {
flash_internal_end = ( uint32_t ) & __flash_end ;
} else {
flash_internal_end = FLASH_BASE + DESIG_FLASH_SIZE * 1024 ;
}
}
}
2018-04-06 19:56:57 +02:00
/** verify if the data is in the internal flash area
* @ param [ in ] address start address of the data to read
* @ param [ in ] size how much data to read or write , in bytes
* @ return if the data is in the internal flash area
*/
2020-01-02 13:42:02 +01:00
static bool flash_internal_range ( uint32_t address , size_t size )
{
2020-02-19 20:58:32 +01:00
if ( 0 = = flash_internal_page | | 0 = = flash_internal_end ) {
flash_internal_init ( ) ;
}
2020-01-01 23:35:56 +01:00
if ( address > ( UINT32_MAX - size ) ) { // on integer overflow will occur
2018-04-06 19:56:57 +02:00
return false ;
}
2020-01-01 23:35:56 +01:00
if ( address < FLASH_BASE ) { // start address is before the start of the internal flash
2018-04-06 19:56:57 +02:00
return false ;
}
2020-02-19 20:58:32 +01:00
if ( ( address + size ) > flash_internal_end ) { // end address is after the end of the internal flash
return false ;
2018-04-06 19:56:57 +02:00
}
return true ;
}
2020-01-02 13:42:02 +01:00
/** get flash page size
* @ return flash page size ( in bytes )
*/
uint16_t flash_internal_page_size ( void )
{
2020-02-19 20:58:32 +01:00
if ( 0 = = flash_internal_page ) { // we don't know the page size yet
2020-03-08 18:17:03 +01:00
switch ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) { // get page size based on family code
case 0x412 : // low-density, 16-32 kB flash
case 0x410 : // medium-density, 64-128 kB flash
2020-02-19 20:58:32 +01:00
flash_internal_page = 1024 ;
2020-03-08 18:17:03 +01:00
break ;
case 0x414 : // high-density, 256-512 kB flash
case 0x430 : // XL-density, 768-1024 kB flash
case 0x418 : // connectivity, 64-256 kB flash
2020-02-19 20:58:32 +01:00
flash_internal_page = 2048 ;
2020-03-08 18:17:03 +01:00
break ;
case 0 : // DBGMCU_IDCODE is only accessible in debug mode (this is a known issue documented in STM32F10xxC/D/E Errata sheet, without workaround)
default : // unknown
2020-01-02 13:42:02 +01:00
if ( DESIG_FLASH_SIZE < 256 ) {
2020-03-08 18:17:03 +01:00
if ( ( * ( uint32_t * ) 0x1FFFF000 & 0xFFFE0000 ) = = 0x20000000 ) { // non-connectivity system memory start detected (MSP address pointing to SRAM
flash_internal_page = 1024 ;
} else { // connectivity system memory start is at 0x1FFFB000
flash_internal_page = 2048 ;
}
2020-01-02 13:42:02 +01:00
} else {
2020-02-19 20:58:32 +01:00
flash_internal_page = 2048 ;
2020-01-02 13:42:02 +01:00
}
2020-03-08 18:17:03 +01:00
break ;
2020-01-02 13:42:02 +01:00
}
}
2020-02-19 20:58:32 +01:00
return flash_internal_page ;
2020-01-02 13:42:02 +01:00
}
2016-08-14 19:25:38 +02:00
bool flash_internal_read ( uint32_t address , uint8_t * buffer , size_t size )
{
2018-02-18 15:21:18 +01:00
// sanity checks
2020-01-01 23:35:56 +01:00
if ( buffer = = NULL | | size = = 0 ) {
2018-02-18 15:21:18 +01:00
return false ;
}
2018-04-06 19:56:57 +02:00
if ( ! flash_internal_range ( address , size ) ) {
2016-08-14 19:25:38 +02:00
return false ;
}
2017-04-15 13:57:02 +02:00
// copy data byte per byte (a more efficient way would be to copy words, than the remaining bytes)
2020-01-01 23:35:56 +01:00
for ( size_t i = 0 ; i < size ; i + + ) {
buffer [ i ] = * ( ( uint8_t * ) address + i ) ;
2016-08-14 19:25:38 +02:00
}
return true ;
}
2018-10-29 12:29:47 +01:00
int32_t flash_internal_write ( uint32_t address , const uint8_t * buffer , size_t size , bool preserve )
2016-08-14 19:25:38 +02:00
{
2018-02-18 15:21:18 +01:00
// sanity checks
2018-10-28 22:50:51 +01:00
if ( buffer = = NULL | | size = = 0 | | size % 2 ) {
return - 1 ;
2020-02-19 20:58:32 +01:00
} else if ( address < FLASH_BASE ) {
2018-10-28 22:50:51 +01:00
return - 2 ;
2020-02-19 20:58:32 +01:00
} else if ( ! flash_internal_range ( address , size ) ) {
2018-10-28 22:50:51 +01:00
return - 3 ;
2016-08-14 19:25:38 +02:00
}
2018-10-29 12:29:47 +01:00
uint32_t written = 0 ; // number of bytes written
2016-08-14 19:25:38 +02:00
flash_unlock ( ) ; // unlock flash to be able to write it
2017-04-15 13:57:02 +02:00
while ( size ) { // write page by page until all data has been written
2018-10-28 22:50:51 +01:00
// verify of we need to erase the flash before writing it
2020-02-19 20:58:32 +01:00
uint32_t page_start = address - ( address % flash_internal_page ) ; // get start of the current page
2020-01-02 13:16:24 +01:00
bool erase = false ; // verify if we need to erase the page
bool identical = true ; // verify if we actually need to write data, or if the data to be written is the identical to the one already if flash
2020-02-19 20:58:32 +01:00
for ( uint32_t i = 0 ; i < size & & ( address + i ) < ( page_start + flash_internal_page ) ; i + = 2 ) { // verify if any word in this page needs to be programmed
2020-01-02 13:16:24 +01:00
if ( * ( uint16_t * ) ( buffer + i ) ! = ( * ( uint16_t * ) ( address + i ) ) ) { // verify if the data to be written is identical to the one already written
identical = false ;
2020-01-06 12:59:57 +01:00
// in theory writing flash is only about flipping (individual) bits from 1 (erase state) to 0
2020-02-19 20:58:32 +01:00
// in practice the micro-controller will only allow to flip individual bits if the whole half-word is erased (set to 0xffff)
2020-01-06 12:59:57 +01:00
if ( * ( uint16_t * ) ( address + i ) ! = 0xffff ) { // flash is not erased
erase = true ; // we need to erase it for it to be written
break ; // no need to check further
}
2016-08-14 19:25:38 +02:00
}
}
2020-01-02 13:16:24 +01:00
if ( identical ) { // no data needs to be changed
// go to end of page, or size
2020-02-19 20:58:32 +01:00
uint32_t remaining = ( page_start + flash_internal_page ) - address ;
2020-01-02 13:16:24 +01:00
if ( remaining > size ) {
remaining = size ;
}
written + = remaining ;
buffer + = remaining ;
address + = remaining ;
size - = remaining ;
} else if ( erase & & preserve ) { // erase before
2020-02-19 20:58:32 +01:00
uint8_t page_data [ flash_internal_page ] ; // a copy of the complete page before the erase it
2017-04-15 13:57:02 +02:00
uint16_t page_i = 0 ; // index for page data
// copy page before address
2020-02-19 20:58:32 +01:00
for ( uint32_t flash = page_start ; flash < address & & flash < ( page_start + flash_internal_page ) & & page_i < flash_internal_page ; flash + + ) {
2017-04-15 13:57:02 +02:00
page_data [ page_i + + ] = * ( uint8_t * ) ( flash ) ;
}
// copy data starting at address
2020-02-19 20:58:32 +01:00
while ( size > 0 & & page_i < flash_internal_page ) {
2017-04-15 13:57:02 +02:00
page_data [ page_i + + ] = * buffer ;
buffer + + ;
address + + ;
size - - ;
}
// copy data after buffer until end of page
2020-02-19 20:58:32 +01:00
while ( page_i < flash_internal_page ) {
2020-01-02 13:16:24 +01:00
page_data [ page_i ] = * ( uint8_t * ) ( page_start + page_i ) ;
2017-04-15 13:57:02 +02:00
page_i + + ;
}
flash_erase_page ( page_start ) ; // erase current page
2018-10-28 22:50:51 +01:00
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
2016-08-14 19:25:38 +02:00
flash_lock ( ) ; // lock back flash to protect it
2018-10-28 22:50:51 +01:00
return - 6 ;
2016-08-14 19:25:38 +02:00
}
2020-02-19 20:58:32 +01:00
for ( uint16_t i = 0 ; i < flash_internal_page ; i + = 2 ) { // write whole page
2020-01-02 13:16:24 +01:00
if ( * ( ( uint16_t * ) ( page_data + i ) ) ! = 0xffff ) { // after an erase the bits are set to one, no need to program them
flash_program_half_word ( page_start + i , * ( ( uint16_t * ) ( page_data + i ) ) ) ;
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
flash_lock ( ) ; // lock back flash to protect it
return - 7 ;
}
2017-04-15 13:57:02 +02:00
}
2018-10-28 22:50:51 +01:00
if ( * ( ( uint16_t * ) ( page_data + i ) ) ! = * ( ( uint16_t * ) ( page_start + i ) ) ) { // verify the programmed data is right
2017-04-15 13:57:02 +02:00
flash_lock ( ) ; // lock back flash to protect it
2018-10-28 22:50:51 +01:00
return - 8 ;
2017-04-15 13:57:02 +02:00
}
2018-10-29 12:29:47 +01:00
written + = 2 ;
2017-04-15 13:57:02 +02:00
}
2018-10-28 22:50:51 +01:00
} else { // simply copy data until end of page (or end of data)
if ( erase ) {
flash_erase_page ( page_start ) ; // erase current page
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
flash_lock ( ) ; // lock back flash to protect it
return - 9 ;
}
}
2020-02-19 20:58:32 +01:00
while ( size > 0 & & address < ( page_start + flash_internal_page ) ) {
2020-01-04 14:36:13 +01:00
if ( * ( ( uint16_t * ) ( buffer ) ) ! = * ( ( uint16_t * ) ( address ) ) & & * ( ( uint16_t * ) ( buffer ) ) ! = 0xffff ) { // only program when data is different and bits need to be set
flash_program_half_word ( address , * ( ( uint16_t * ) ( buffer ) ) ) ; // program the data
2020-01-02 13:16:24 +01:00
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
flash_lock ( ) ; // lock back flash to protect it
return - 10 ;
}
2020-01-04 14:36:13 +01:00
if ( * ( ( uint16_t * ) address ) ! = * ( ( uint16_t * ) buffer ) ) { // verify the programmed data is right
flash_lock ( ) ; // lock back flash to protect it
return - 11 ;
}
2017-04-15 13:57:02 +02:00
}
2018-10-29 12:29:47 +01:00
written + = 2 ;
2017-04-15 13:57:02 +02:00
buffer + = 2 ;
address + = 2 ;
size - = 2 ;
2016-08-14 19:25:38 +02:00
}
}
}
flash_lock ( ) ; // lock back flash to protect it
2018-10-29 12:29:47 +01:00
return written ;
2016-08-14 19:25:38 +02:00
}
2020-01-02 18:26:12 +01:00
2020-02-19 20:59:26 +01:00
/* the EEPROM allocated area is erased at first
* the EEPROM data starts at the end of the allocated memory
* each time it is written , the next data segment is placed before the existing one
* a data segment start with the size , which help detecting the segment since the data can be the same as erased data ( 0xffff )
*/
2020-01-02 18:26:12 +01:00
void flash_internal_eeprom_setup ( uint16_t pages )
{
2020-02-19 20:59:26 +01:00
if ( 0 = = flash_internal_page | | 0 = = flash_internal_end ) {
flash_internal_init ( ) ; // get page size and flash end
2020-01-02 18:26:12 +01:00
}
2020-02-19 20:59:26 +01:00
flash_internal_eeprom_start = 0 ; // reset start address
flash_internal_eeprom_address = 0 ; // reset EEPROM address
if ( pages > DESIG_FLASH_SIZE * 1024 / flash_internal_page ) { // not enough pages are available
return ;
2020-01-02 18:26:12 +01:00
}
2020-02-19 20:59:26 +01:00
flash_internal_eeprom_start = flash_internal_end - flash_internal_page * pages ; // set EEPROM start (page aligned)
// find EEPROM in flash (first non-erased word)
for ( flash_internal_eeprom_address = flash_internal_eeprom_start ; flash_internal_eeprom_address < flash_internal_end & & 0xffff = = * ( uint16_t * ) flash_internal_eeprom_address ; flash_internal_eeprom_address + = 2 ) ;
2020-01-02 18:26:12 +01:00
}
bool flash_internal_eeprom_read ( uint8_t * eeprom , uint16_t size )
{
// sanity checks
2020-02-19 20:59:26 +01:00
if ( NULL = = eeprom | | 0 = = size | | 0xffff = = size | | 0 = = flash_internal_eeprom_start | | 0 = = flash_internal_eeprom_address ) {
2020-01-02 18:26:12 +01:00
return false ;
}
2020-02-19 20:59:26 +01:00
if ( size + 2U > flash_internal_end - flash_internal_eeprom_start ) { // not enough space
return false ;
}
if ( size + 2U > flash_internal_end - flash_internal_eeprom_address ) { // EEPROM size is too large
2020-01-02 18:26:12 +01:00
return false ;
}
if ( size ! = * ( uint16_t * ) flash_internal_eeprom_address ) { // check if size match
return false ;
}
return flash_internal_read ( flash_internal_eeprom_address + 2 , eeprom , size ) ; // read data
}
int32_t flash_internal_eeprom_write ( const uint8_t * eeprom , uint16_t size )
{
// sanity checks
2020-02-19 20:59:26 +01:00
if ( NULL = = eeprom | | 0 = = size | | 0xffff = = size | | 0 = = flash_internal_eeprom_start | | 0 = = flash_internal_eeprom_address ) {
2020-01-02 18:26:12 +01:00
return - 1 ;
}
2020-02-19 20:59:26 +01:00
if ( size + 2U > flash_internal_end - flash_internal_eeprom_start ) { // not enough space
2020-01-02 18:26:12 +01:00
return - 2 ;
}
2020-02-19 20:59:26 +01:00
if ( flash_internal_eeprom_start + size + 2U > flash_internal_eeprom_address ) { // there is not enough free space
// erase all EEPROM allocated pages
flash_unlock ( ) ; // unlock flash to be able to erase it
for ( uint32_t page_start = flash_internal_eeprom_start ; page_start < flash_internal_end ; page_start + = flash_internal_page ) {
flash_erase_page ( page_start ) ; // erase current page
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
flash_lock ( ) ; // lock back flash to protect it
return - 3 ;
2020-01-02 18:26:12 +01:00
}
}
2020-02-19 20:59:26 +01:00
flash_internal_eeprom_address = flash_internal_end ; // put address back as the end
2020-01-02 18:26:12 +01:00
}
2020-02-19 20:59:26 +01:00
flash_internal_eeprom_address - = ( size + 2U ) ; // get new start of data segment
if ( flash_internal_eeprom_address % 2 ) { // have segment word aligned
flash_internal_eeprom_address - - ;
2020-01-02 18:26:12 +01:00
}
2020-02-19 20:59:26 +01:00
if ( flash_internal_eeprom_address < flash_internal_eeprom_start ) { // just to be sure
return - 4 ;
2020-01-02 18:26:12 +01:00
}
2020-02-19 20:59:26 +01:00
int32_t rc = flash_internal_write ( flash_internal_eeprom_address , ( uint8_t * ) & size , 2 , false ) ; // write size
2020-01-02 18:26:12 +01:00
if ( 2 ! = rc ) {
return ( - 10 + rc ) ;
}
2020-02-19 20:59:26 +01:00
rc = flash_internal_write ( flash_internal_eeprom_address + 2 , eeprom , size , false ) ; // write data
2020-01-02 18:26:12 +01:00
if ( size ! = rc ) {
return ( - 10 + rc ) ;
}
return rc ;
}
2020-03-08 18:17:37 +01:00
uint16_t flash_internal_probe_size ( void )
{
if ( 0 = = DESIG_FLASH_SIZE ) { // no flash size advertised
return 0 ;
}
// get max flash size based on device identifier (DEV_ID)
uint32_t flash_size_max = 0 ; // max flash size (in bytes)
switch ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) { // get page size based on family code
case 0x412 : // low-density, 16-32 kB flash
flash_size_max = 32 ;
break ;
case 0x410 : // medium-density, 64-128 kB flash
flash_size_max = 128 ;
break ;
case 0x414 : // high-density, 256-512 kB flash
flash_size_max = 512 ;
break ;
case 0x430 : // XL-density, 768-1024 kB flash
flash_size_max = 1024 ;
break ;
case 0x418 : // connectivity, 64-256 kB flash
flash_size_max = 256 ;
break ;
case 0 : // DBGMCU_IDCODE is only accessible in debug mode (this is a known issue documented in STM32F10xxC/D/E Errata sheet, without workaround)
default : // unknown
if ( ( * ( uint32_t * ) 0x1FFFF000 & 0xFFFE0000 ) = = 0x20000000 ) { // non-connectivity system memory start detected (MSP address pointing to SRAM
switch ( DESIG_FLASH_SIZE ) {
case 16 :
case 32 :
flash_size_max = 32 ; // low-density, 16-32 kB flash
break ;
case 64 :
case 128 :
flash_size_max = 128 ; // medium-density, 64-128 kB flash
break ;
case 256 :
case 512 :
flash_size_max = 512 ; // high-density, 256-512 kB flash
break ;
case 768 :
case 1024 :
flash_size_max = 1024 ; // XL-density, 768-1024 kB flash
break ;
default :
break ;
}
} else { // connectivity system memory start is at 0x1FFFB000
flash_size_max = 256 ; // connectivity, 64-256 kB flash
}
break ;
}
if ( 0 = = flash_size_max ) { // could not determine max flash size
return 0 ;
}
flash_size_max * = 1024 ; // get in bytes
// test if page is writable, starting with last one
uint32_t flash_size ; // tested flash size (in bytes)
const uint16_t test_data = 0x2342 ; // the data we will write and read to test page
flash_unlock ( ) ; // unlock flash to be able to write it
for ( flash_size = DESIG_FLASH_SIZE * 1024 - flash_internal_page_size ( ) ; flash_size < flash_size_max ; flash_size + = flash_internal_page_size ( ) ) { // don't exceed max size else it will erase the first page (weird behaviour)
uint32_t address = FLASH_BASE + flash_size ;
flash_erase_page ( address ) ; // erase current page
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
break ;
}
flash_program_half_word ( address , test_data ) ; // writes test data
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
break ;
}
if ( test_data ! = * ( ( uint16_t * ) address ) ) { // verify data is written correctly
break ;
}
flash_erase_page ( address ) ; // erase test data
if ( flash_get_status_flags ( ) ! = FLASH_SR_EOP ) { // operation went wrong
break ;
}
}
flash_lock ( ) ;
if ( flash_size < DESIG_FLASH_SIZE * 1024 ) { // less than advertised size
return 0 ;
} else {
return flash_size / 1024 ;
}
}