/** library to read/write internal flash * @file * @author King Kévin * @copyright SPDX-License-Identifier: GPL-3.0-or-later * @date 2016-2020 * @note peripherals used: none */ /* standard libraries */ #include // standard integer types #include // general utilities /* STM32 (including CM3) libraries */ #include // flash utilities #include // device signature definitions #include // debug definitions /* own libraries */ #include "flash_internal.h" // flash storage library API #include "global.h" // global 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; /** 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; } } } /** 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 return false; } return true; } /** get flash page size * @return flash page size (in bytes) */ uint16_t flash_internal_page_size(void) { if (0 == flash_internal_page) { // we don't know the page size yet 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 flash_internal_page = 1024; break; case 0x414: // high-density, 256-512 kB flash case 0x430: // XL-density, 768-1024 kB flash case 0x418: // connectivity, 64-256 kB flash flash_internal_page = 2048; 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 (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; } break; } } 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; } // 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 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; } } } 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; } 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; } }