flash_internal: remove F1 flash utilities, add F4 section utility

compared to the STM32F1, the STM32F4 does not used 1 KB flash pages.
F4 uses variable large (>= 16 KB) flash sections.
this makes using the last page (128 KB instead of 1KB) for EEPROM highly inefficient.
caching such large pages before reprogramming small portion is also no doable (there is not enough RAM).
thus almost all F1 utilities are not applicable anymore.
to help erasing the right section, a utility to get the section from an address is added.
This commit is contained in:
King Kévin 2020-11-24 16:04:37 +01:00
parent 9db9ea9dc1
commit 8a165c4d71
2 changed files with 105 additions and 348 deletions

View File

@ -1,4 +1,4 @@
/** library to read/write internal flash
/** internal flash utilities
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -11,286 +11,110 @@
/* STM32 (including CM3) libraries */
#include <libopencmsis/core_cm3.h> // Cortex M3 utilities
#include <libopencm3/stm32/flash.h> // flash utilities
#include <libopencm3/stm32/desig.h> // device signature definitions
#include <libopencm3/stm32/dbgmcu.h> // MCU definitions
/* own libraries */
#include "flash_internal.h" // flash storage library API
#include "global.h" // global definitions
#include "flash_internal.h" // own definitions
/** flash page size */
static uint16_t flash_internal_page = 0;
/** end address of flash */
static uint32_t flash_internal_end = 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;
/** information about the MCU's flash sections */
struct flash_sections_info_s {
const uint16_t device_id; /**< the MCU DEV ID */
const uint8_t number; /**< number of sections */
const struct flash_internal_section_info_s* sections; /**< size of the sections, in KiB */
};
/** find out page size and flash end address */
static void flash_internal_init(void)
/** information about the STM42F401xB/C flash sections */
static const struct flash_internal_section_info_s sections_f401xbc[] = {
{
.number = 0,
.size = 16,
.start = FLASH_BASE + (0) * 1024,
.end = FLASH_BASE + (16) * 1024 - 1,
},
{
.number = 1,
.size = 16,
.start = FLASH_BASE + (0 + 16) * 1024,
.end = FLASH_BASE + (16 + 16) * 1024 - 1,
},
{
.number = 2,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16) * 1024 - 1,
},
{
.number = 3,
.size = 16,
.start = FLASH_BASE + (0 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16) * 1024 - 1,
},
{
.number = 4,
.size = 64,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64) * 1024 - 1,
},
{
.number = 5,
.size = 128,
.start = FLASH_BASE + (0 + 16 + 16 + 16 + 16 + 64) * 1024,
.end = FLASH_BASE + (16 + 16 + 16 + 16 + 64 + 128) * 1024 - 1,
},
};
static const struct flash_sections_info_s flash_sections_info[] = {
{
.device_id = 0x423,
.number = LENGTH(sections_f401xbc),
.sections = sections_f401xbc,
},
};
bool flash_internal_range(uint32_t address, size_t size)
{
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;
}
}
}
/** 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
*/
static bool flash_internal_range(uint32_t address, size_t size)
{
if (0 == flash_internal_page || 0 == flash_internal_end) {
flash_internal_init();
}
if (address > (UINT32_MAX - size)) { // on integer overflow will occur
return false;
}
if (address < FLASH_BASE) { // start address is before the start of the internal flash
return false;
}
if ((address + size) > flash_internal_end) { // end address is after the end of the internal flash
if (address > (UINT32_MAX - size)) { // an integer overflow will occur
return false;
}
if ((address + size) > FLASH_BASE + desig_get_flash_size() * 1024) { // end address is after the end of the internal flash
return false;
}
return true;
}
/** get flash page size
* @return flash page size (in bytes)
/** find out in which section is this address
* @param[in] address address to find the section for
* @return section in which this address is (NULL if not in flash, section has not been found, or the sections of this device are unknown)
*/
uint16_t flash_internal_page_size(void)
const struct flash_internal_section_info_s* flash_internal_section(uint32_t address)
{
if (0 == flash_internal_page) { // we don't know the page size yet
if (DESIG_FLASH_SIZE < 256) {
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;
}
} else {
flash_internal_page = 2048;
if (!flash_internal_range(address, 0)) { // verify if this address is in flash
return NULL;
}
// find sections information for this device
uint8_t flash_sections_info_i;
for (flash_sections_info_i = 0; flash_sections_info_i < LENGTH(flash_sections_info); flash_sections_info_i++) {
if ((DBGMCU_IDCODE & DBGMCU_IDCODE_DEV_ID_MASK) == flash_sections_info[flash_sections_info_i].device_id) {
break; // we found the device's section information
}
}
return flash_internal_page;
}
bool flash_internal_read(uint32_t address, uint8_t *buffer, size_t size)
{
// sanity checks
if (buffer == NULL || size == 0) {
return false;
}
if (!flash_internal_range(address, size)) {
return false;
if (flash_sections_info_i >= LENGTH(flash_sections_info)) { // we did not find the device's section information
return NULL;
}
// copy data byte per byte (a more efficient way would be to copy words, than the remaining bytes)
for (size_t i = 0; i < size; i++) {
buffer[i] = *((uint8_t*)address + i);
}
return true;
}
int32_t flash_internal_write(uint32_t address, const uint8_t *buffer, size_t size, bool preserve)
{
// sanity checks
if (buffer == NULL || size == 0 || size % 2) {
return -1;
} else if (address < FLASH_BASE) {
return -2;
} else if (!flash_internal_range(address, size)) {
return -3;
}
uint32_t written = 0; // number of bytes written
flash_unlock(); // unlock flash to be able to write it
while (size) { // write page by page until all data has been written
// verify of we need to erase the flash before writing it
uint32_t page_start = address - (address % flash_internal_page); // get start of the current page
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 + flash_internal_page); i += 2) { // verify if any word in this page needs to be programmed
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;
// in theory writing flash is only about flipping (individual) bits from 1 (erase state) to 0
// in practice the micro-controller will only 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
}
}
}
if (identical) { // no data needs to be changed
// go to end of page, or size
uint32_t remaining = (page_start + flash_internal_page) - address;
if (remaining > size) {
remaining = size;
}
written += remaining;
buffer += remaining;
address += remaining;
size -= remaining;
} else if (erase && preserve) { // erase before
uint8_t page_data[flash_internal_page]; // a copy of the complete page before the erase it
uint16_t page_i = 0; // index for page data
// copy page before address
for (uint32_t flash = page_start; flash < address && flash < (page_start + flash_internal_page) && page_i <flash_internal_page; flash++) {
page_data[page_i++] = *(uint8_t*)(flash);
}
// copy data starting at address
while (size > 0 && page_i < flash_internal_page) {
page_data[page_i++] = *buffer;
buffer++;
address++;
size--;
}
// copy data after buffer until end of page
while (page_i < flash_internal_page) {
page_data[page_i] = *(uint8_t*)(page_start + page_i);
page_i++;
}
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 -6;
}
for (uint16_t i = 0; i < flash_internal_page; i += 2) { // write whole page
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;
}
}
if (*((uint16_t*)(page_data + i)) != *((uint16_t*)(page_start + i))) { // verify the programmed data is right
flash_lock(); // lock back flash to protect it
return -8;
}
written += 2;
}
} 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 + flash_internal_page)) {
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
if (flash_get_status_flags() != FLASH_SR_EOP) { // operation went wrong
flash_lock(); // lock back flash to protect it
return -10;
}
if (*((uint16_t*)address) != *((uint16_t*)buffer)) { // verify the programmed data is right
flash_lock(); // lock back flash to protect it
return -11;
}
}
written += 2;
buffer += 2;
address += 2;
size -= 2;
}
// find in which section this address is
for (uint8_t i = 0; i < flash_sections_info[flash_sections_info_i].number; i++) {
if (address >= flash_sections_info[flash_sections_info_i].sections[i].start && address <= flash_sections_info[flash_sections_info_i].sections[i].end) {
return &flash_sections_info[flash_sections_info_i].sections[i];
}
}
flash_lock(); // lock back flash to protect it
return written;
}
/* 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)
*/
void flash_internal_eeprom_setup(uint16_t pages)
{
if (0 == flash_internal_page || 0 == flash_internal_end) {
flash_internal_init(); // get page size and flash end
}
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;
}
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);
}
bool flash_internal_eeprom_read(uint8_t *eeprom, uint16_t size)
{
// sanity checks
if (NULL == eeprom || 0 == size || 0xffff == size || 0 == flash_internal_eeprom_start || 0 == flash_internal_eeprom_address) {
return false;
}
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
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 || 0xffff == size || 0 == flash_internal_eeprom_start || 0 == flash_internal_eeprom_address) {
return -1;
}
if (size + 2U > flash_internal_end - flash_internal_eeprom_start) { // not enough space
return -2;
}
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;
}
}
flash_internal_eeprom_address = flash_internal_end; // put address back as the end
}
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--;
}
if (flash_internal_eeprom_address < flash_internal_eeprom_start) { // just to be sure
return -4;
}
int32_t rc = flash_internal_write(flash_internal_eeprom_address, (uint8_t*)&size, 2, false); // write size
if (2 != rc) {
return (-10 + rc);
}
rc = flash_internal_write(flash_internal_eeprom_address + 2, eeprom, size, false); // write data
if (size != rc) {
return (-10 + rc);
}
return rc;
return NULL; // we did not find the section
}
uint32_t flash_internal_probe_read_size(void)
@ -310,55 +134,3 @@ uint32_t flash_internal_probe_read_size(void)
return address - 1 - FLASH_BASE;
}
uint32_t flash_internal_probe_write_size(void)
{
if (0 == DESIG_FLASH_SIZE) { // no flash size advertised
return 0;
}
// prepare for reading the flash
cm_disable_faults(); // disable all faults, particularly BusFault
SCB_CFSR |= SCB_CFSR_BFARVALID; // clear bus fault flag
SCB_CCR |= SCB_CCR_BFHFNMIGN; // ignore bus faults (but still flag them)
// prepare for writing the flash
flash_unlock(); // unlock flash to be able to write it
// try reading and writing the flash, page per page
uint32_t start = FLASH_BASE + DESIG_FLASH_SIZE * 1024; // start with the end of the advertised flash
if ((uint32_t)&__flash_end >= FLASH_BASE) { // use linker flash size if provided
start = (uint32_t)&__flash_end;
}
uint32_t address = start; // address to test
const uint16_t test_data = 0x2342; // the data we will write and read to test page
while (address < 0x1FFFEFFF) { // this is where the system memory starts
// try reading the flash
(void)*(volatile uint32_t*)address; // access address
if (0 != (SCB_CFSR & SCB_CFSR_BFARVALID)) { // until a bus fault occurs
break; // page not readable
}
// try writing the flash
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;
}
address += flash_internal_page_size(); // go to next page
}
flash_clear_status_flags(); // clear all flag
flash_lock(); // protect again from writing
SCB_CFSR |= SCB_CFSR_BFARVALID; // clear bus fault flag
SCB_CCR &= ~SCB_CCR_BFHFNMIGN; // re-enable bus fault
cm_enable_faults(); // re-enable faults
return address - start;
}

View File

@ -1,4 +1,4 @@
/** library to read/write internal flash
/** internal flash utilities
* @file
* @author King Kévin <kingkevin@cuvoodoo.info>
* @copyright SPDX-License-Identifier: GPL-3.0-or-later
@ -7,48 +7,33 @@
*/
#pragma once
/** read data from internal flash
* @param[in] address start address of the data to read
* @param[out] buffer where to store the read data
* @param[in] size how much data to read, in bytes
* @return if read succeeded
/** base address of the flash memory
* @note not sure if there is an STM32F4 with another address, or why this is not defined in libopencm3
*/
bool flash_internal_read(uint32_t address, uint8_t *buffer, size_t size);
/** write data to internal flash
* @param[in] address start address where to write data to
* @param[in] buffer data to be written
* @param[in] size how much data to write, in bytes
* @param[in] preserve keep the rest of the page if data needs to be erased
* @return number of bytes written (including preserved data), or negative in case of error
* @note the page will be erased if needed to write the data to the flash
*/
int32_t flash_internal_write(uint32_t address, const uint8_t *buffer, size_t size, bool preserve);
/** get flash page size
* @return flash page size (in bytes)
*/
uint16_t flash_internal_page_size(void);
#define FLASH_BASE (0x08000000U)
/** setup the emulated EEPROM area
* @param[in] pages number of flash pages to allocate for the emulated EEPROM
* @warn area must be at least 4 bytes larger than the structure to write
/** information about a flash section */
struct flash_internal_section_info_s {
const uint8_t number; /**< section number */
const uint8_t size; /**< section size, in KiB */
const uint32_t start; /**< section start address */
const uint32_t end; /**< section end address */
};
/** 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
*/
void flash_internal_eeprom_setup(uint16_t pages);
/** read emulated EEPROM area
* @param[out] eeprom where to store the EEPROM data
* @param[in] size size of the EEPROM area (in bytes)
bool flash_internal_range(uint32_t address, size_t size);
/** find out in which section is this address
* @param[in] address address to find the section for
* @return section in which this address is (NULL if not in flash, section has not been found, or the sections of this device are unknown)
*/
bool flash_internal_eeprom_read(uint8_t *eeprom, uint16_t size);
/** write emulated EEPROM area
* @param[in] eeprom EEPROM data to be stored
* @param[in] size size of the EEPROM area (in bytes)
* @return number of bytes written (including preserved data), or negative in case of error
*/
int32_t flash_internal_eeprom_write(const uint8_t *eeprom, uint16_t size);
const struct flash_internal_section_info_s* flash_internal_section(uint32_t address);
/** probe the readable size of the internal flash
* @return tested size (in bytes)
*/
uint32_t flash_internal_probe_read_size(void);
/** probe the additional writable size of the internal flash, after the advertised size (and linker provided)
* @return tested size (in bytes)
*/
uint32_t flash_internal_probe_write_size(void);