/** 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 // Cortex M3 utilities #include // flash utilities #include // device signature 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_get_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 if (desig_get_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; } } 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_get_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; } uint32_t flash_internal_probe_read_size(void) { // we will check is a flash address is readable until a bus fault occurs 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) uint32_t address = FLASH_BASE; // start with the start of flash while (0 == (SCB_CFSR & SCB_CFSR_BFARVALID)) { // until a bus fault occurs (void)*(volatile uint8_t*)address; // access address address++; // got to next address } 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 - 1 - FLASH_BASE; } uint32_t flash_internal_probe_write_size(void) { if (0 == desig_get_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_get_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; }