2016-08-14 19:25:38 +02:00
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*
*/
/** library to read/write internal flash (code)
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-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-01-02 18:26:12 +01:00
/** number of flash pages, located at the end of flash memory, to use for EEPROM functionality */
static uint16_t flash_internal_eeprom_pages = 0 ;
/** 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 ;
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-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-01-10 11:04:20 +01:00
if ( ( uint32_t ) & __flash_end > = FLASH_BASE ) { // check if the end for the internal flash is enforced by the linker script
2020-01-01 23:35:56 +01:00
if ( ( address + size ) > ( uint32_t ) & __flash_end ) { // end address is after the end of the enforced internal flash
2018-04-06 19:56:57 +02:00
return false ;
}
} else {
2020-01-01 23:35:56 +01:00
if ( ( address + size ) > ( FLASH_BASE + DESIG_FLASH_SIZE * 1024 ) ) { // end address is after the end of the advertised flash
2018-04-06 19:56:57 +02:00
return false ;
}
}
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 )
{
static uint16_t page_size = 0 ; // remember page size
if ( page_size ) { // we already determined the size
return page_size ;
}
if ( 0 = = page_size ) { // we don't know the page size yet
2020-01-02 13:46:53 +01:00
if ( ( 0x410 = = ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) ) | | ( 0x412 = = ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) ) ) { // low-density (16-32 KB flash) and medium-density (64-128 KB flash) devices have 1 KB flash pages
2020-01-02 13:42:02 +01:00
page_size = 1024 ;
2020-01-02 13:46:53 +01:00
} else if ( ( 0x414 = = ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) ) | | ( 0x430 = = ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) ) | | ( 0x418 = = ( DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK ) ) ) { // high-density (256-512 KB flash), XL-density (768-1024 KB flash) devices and connectivity line have 2 KB flash pages
2020-01-02 13:42:02 +01:00
page_size = 2048 ;
} else { // unknown device type (or unreadable type, see errata), deduce page size from flash size
if ( DESIG_FLASH_SIZE < 256 ) {
page_size = 1024 ;
} else {
page_size = 2048 ;
}
}
}
return page_size ;
}
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 ;
2018-04-06 19:56:57 +02:00
}
if ( ! flash_internal_range ( address , size ) ) {
2018-10-28 22:50:51 +01:00
return - 2 ;
2018-02-18 15:21:18 +01:00
}
// verify if it's in the flash area
2018-10-28 22:50:51 +01:00
if ( address < FLASH_BASE ) {
return - 3 ;
} else if ( ( uint32_t ) & __flash_end > = FLASH_BASE & & ( address + size ) > ( uint32_t ) & __flash_end ) {
2020-01-04 14:35:28 +01:00
return - 4 ;
2020-01-02 13:46:53 +01:00
} else if ( ( uint32_t ) & __flash_end < FLASH_BASE & & ( address + size ) > ( FLASH_BASE + DESIG_FLASH_SIZE * 1024 ) ) {
2018-10-28 22:50:51 +01:00
return - 5 ;
2016-08-14 19:25:38 +02:00
}
2020-01-02 13:42:02 +01:00
uint16_t page_size = flash_internal_page_size ( ) ; // get page size
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
uint32_t page_start = address - ( address % page_size ) ; // 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
for ( uint32_t i = 0 ; i < size & & ( address + i ) < ( page_start + page_size ) ; i + = 2 ) { // verify if no bit needs to be flipped to 1 again
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
// in practice the micro-controller won't allow to flip individual bits if the whole half-word is erased (set to 0xffff)
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
uint32_t remaining = ( page_start + page_size ) - address ;
if ( remaining > size ) {
remaining = size ;
}
written + = remaining ;
buffer + = remaining ;
address + = remaining ;
size - = remaining ;
} else if ( erase & & preserve ) { // erase before
2018-02-18 15:21:18 +01:00
uint8_t page_data [ page_size ] ; // 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
2018-10-28 22:50:51 +01:00
for ( uint32_t flash = page_start ; flash < address & & flash < ( page_start + page_size ) & & page_i < page_size ; flash + + ) {
2017-04-15 13:57:02 +02:00
page_data [ page_i + + ] = * ( uint8_t * ) ( flash ) ;
}
// copy data starting at address
2018-10-28 22:50:51 +01:00
while ( size > 0 & & page_i < page_size ) {
2017-04-15 13:57:02 +02:00
page_data [ page_i + + ] = * buffer ;
buffer + + ;
address + + ;
size - - ;
}
// copy data after buffer until end of page
2018-10-28 22:50:51 +01:00
while ( page_i < page_size ) {
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
}
2018-10-28 22:50:51 +01:00
for ( uint16_t i = 0 ; i < page_size ; 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 ;
}
}
while ( size > 0 & & address < ( page_start + page_size ) ) {
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
void flash_internal_eeprom_setup ( uint16_t pages )
{
flash_internal_eeprom_pages = pages ; // just need to remember the number of pages
// get allocated memory address
2020-01-10 11:04:20 +01:00
if ( ( uint32_t ) & __flash_end > = FLASH_BASE ) { // check if the end for the internal flash is enforced by the linker script
2020-01-02 18:26:12 +01:00
flash_internal_eeprom_start = ( uint32_t ) & __flash_end - flash_internal_eeprom_pages * flash_internal_page_size ( ) ;
} else {
flash_internal_eeprom_start = ( FLASH_BASE + DESIG_FLASH_SIZE * 1024 ) - flash_internal_eeprom_pages * flash_internal_page_size ( ) ;
}
flash_internal_eeprom_start - = flash_internal_eeprom_start % flash_internal_page_size ( ) ; // ensure it starts at start of page
// find EEPROM in flash
flash_internal_eeprom_address = flash_internal_eeprom_start ; // by default start with start of allocated flash memory
for ( uint32_t addr = flash_internal_eeprom_start ; addr < flash_internal_eeprom_start + flash_internal_eeprom_pages * flash_internal_page_size ( ) - 2 ; addr + = 2 ) {
if ( 0 ! = * ( uint16_t * ) addr ) { // 0 is invalidated flash
flash_internal_eeprom_address = addr ; // we found a valid address, which should be the size of the EEPROM
break ;
}
}
uint16_t size = * ( uint16_t * ) flash_internal_eeprom_address ;
if ( size + 2U > flash_internal_eeprom_pages * flash_internal_page_size ( ) ) { // there is not enough space
flash_internal_eeprom_address = flash_internal_eeprom_start ; // set back to start
}
if ( 0 ! = size & & flash_internal_eeprom_address + 2U + size > flash_internal_eeprom_start + flash_internal_eeprom_pages * flash_internal_page_size ( ) ) { // the size seems to be valid to there is not enough remaining space
flash_internal_eeprom_address = flash_internal_eeprom_start ; // set back to start
}
}
bool flash_internal_eeprom_read ( uint8_t * eeprom , uint16_t size )
{
// sanity checks
if ( NULL = = eeprom | | 0 = = size | | 0 = = flash_internal_eeprom_pages | | 0 = = flash_internal_eeprom_start | | 0 = = flash_internal_eeprom_address ) {
return false ;
}
if ( size + 2U > flash_internal_eeprom_pages * flash_internal_page_size ( ) ) { // not enough space
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
if ( NULL = = eeprom | | 0 = = size | | 0 = = flash_internal_eeprom_pages | | 0 = = flash_internal_eeprom_start | | 0 = = flash_internal_eeprom_address ) {
return - 1 ;
}
if ( size + 2U > flash_internal_eeprom_pages * flash_internal_page_size ( ) ) { // not enough space
return - 2 ;
}
uint16_t current_size = * ( uint16_t * ) flash_internal_eeprom_address ;
if ( size = = current_size ) { // check if it already the same
bool identical = true ;
for ( uint16_t i = 0 ; i < size ; i + + ) {
if ( eeprom [ i ] ! = * ( ( uint8_t * ) flash_internal_eeprom_address + 2 + i ) ) {
identical = false ;
break ;
}
}
if ( identical ) { // no need to write since it's identical
return size ;
}
}
// one optimisation would be to check if we just need to flip bits to 0, than we could reuse the same location
// invalidate current EEPROM
const uint8_t zero [ 2 ] = { 0 , 0 } ;
flash_internal_write ( flash_internal_eeprom_address , zero , 2 , false ) ;
flash_internal_eeprom_address + = 2 ;
while ( current_size & & flash_internal_eeprom_address < flash_internal_eeprom_start + flash_internal_eeprom_pages * flash_internal_page_size ( ) ) {
flash_internal_write ( flash_internal_eeprom_address , zero , 2 , false ) ;
current_size - = 2 ;
flash_internal_eeprom_address + = 2 ;
}
// go to start if there is not enough remaining space
if ( flash_internal_eeprom_address + size + 2U > flash_internal_eeprom_start + flash_internal_eeprom_pages * flash_internal_page_size ( ) ) {
flash_internal_eeprom_address = flash_internal_eeprom_start ;
}
int32_t rc = flash_internal_write ( flash_internal_eeprom_address , ( uint8_t * ) & size , 2 , false ) ;
if ( 2 ! = rc ) {
return ( - 10 + rc ) ;
}
rc = flash_internal_write ( flash_internal_eeprom_address + 2 , eeprom , size , false ) ;
if ( size ! = rc ) {
return ( - 10 + rc ) ;
}
return rc ;
}