/** * eeprom_emulation.c * * Uses the microcontroller Flash memory as if it were EEPROM. All "EEPROM" * variables are saved in Flash with an associated variable * address. Saving a new value to a particular address actually * just stores a new variable/address combo. * When reading, the most recently written value is returned. If * no value was written, a default value (passed to the read * function) is returned instead. * Erases are only performed when a page becomes completely full. * The most recent saved value for each variable is transferred to * the alternate storage page, and the alternate page is marked as * active. * * Copyright (c) 2022 Bigscreen, Inc. */ #include #include #include "flash_mutex.h" #include "eeprom_emulation.h" SemaphoreHandle_t EEPROM_Mutex_Handle; // Only used for read/write to eeprom // separate flash mutex used for all flash operations ("flash_mutex.h/c") uint32_t EEPROM_Keys[] = { (uint32_t)EEKEY_TotalPoweredOnTime10min, (uint32_t)EEKEY_TotalDisplaysOnTime10min_Left, (uint32_t)EEKEY_LongestDisplaysOnTime10min, (uint32_t)EEKEY_TotalDisplaysOnTime10min_Right, (uint32_t)EEKEY_NumTimesBlock0Erased, (uint32_t)EEKEY_NumTimesBlock1Erased }; uint32_t Num_EEPROM_Keys = 5; bool EEPROM_Finished_Init = false; /* Forward declaration of private functions */ static uint32_t write64bit(uint32_t addr, uint32_t data0, uint32_t data1); static uint32_t eraseblock(EEPROM_Block_T block); static EEPROM_Status_T getblockstatus(EEPROM_Block_T block); static uint32_t setblockstatus(EEPROM_Block_T block, EEPROM_Status_T status); static uint32_t checkblockerasedandfix(EEPROM_Block_T block); static uint32_t formateeprom(void); static uint32_t blocktransfer(EEPROM_Block_T source, EEPROM_Block_T dest); static EEPROM_Read_Status_T readvariable(EEPROM_Block_T block, uint32_t key, uint32_t* data); static EEPROM_Write_Status_T writevariable(EEPROM_Block_T block, uint32_t key, uint32_t data); static EEPROM_Block_T findvalidblock(EEPROM_Operation_T oper); /* Private function definitions */ static uint32_t write64bit(uint32_t addr, uint32_t data0, uint32_t data1) { uint32_t retval; // writes two 32-bit words to a 64-bit section of Flash. // 64 bits is the minimum write size uint32_t place_within_page = addr % PAGE_SIZE; uint32_t flash_page = addr / PAGE_SIZE; // First write to the latch buffer. Any page in Flash works the // same. Just using the first page for simplicity Flash_Mutex_Lock(); uint32_t* first_flash_page = (uint32_t*)(IFLASH_ADDR); first_flash_page[(place_within_page/sizeof(uint32_t))] = data0; first_flash_page[(place_within_page/sizeof(uint32_t)) + 1] = data1; // then issue the flash write command retval = efc_perform_command(EFC, EFC_FCMD_WP, flash_page); Flash_Mutex_Unlock(); return retval; } static uint32_t eraseblock(EEPROM_Block_T block) { uint32_t retval; uint32_t farg; if(block == EEPROM_Block0) { farg = ((EEPROM_BLOCK0_START_PAGE / 16)<<4) | 0x02; } else { farg = ((EEPROM_BLOCK1_START_PAGE / 16)<<4) | 0x02; } Flash_Mutex_Lock(); retval = efc_perform_command(EFC, EFC_FCMD_EPA, farg); Flash_Mutex_Unlock(); return retval; } static EEPROM_Status_T getblockstatus(EEPROM_Block_T block) { uint32_t* block_start; EEPROM_Status_T retval = EEPROM_Invalid; if(block == EEPROM_Block0) { block_start = (uint32_t*)(EEPROM_BLOCK0_START); } else { block_start = (uint32_t*)(EEPROM_BLOCK1_START); } // Status flags - // When page is empty, it will just have the default Flash // erased values of 0xFF for all bytes. We can check for empty // by comparing the first 8 bytes (64-bits, minimum write size) // to 0xFF if((block_start[0] == FLASH_ERASED_32B) && (block_start[1] == FLASH_ERASED_32B)) { retval = EEPROM_Erased; } // When the page has been marked to transfer data, the first 8 bytes // are flagged with a special value if((block_start[0] == FLASH_TAGGED_32B) && (block_start[1] == FLASH_TAGGED_32B)) { retval = EEPROM_Transferring; } // And when the first 8 bytes are set to zero, this is the active page // and is receiving any new data values saved if((block_start[0] == 0u) && (block_start[1] == 0u)) { retval = EEPROM_Active; } // If any other combination is set for those first 8 bytes, then // the status is invalid. retval will not have been changed from // its initial "EE_Page_Invalid" value return retval; } static uint32_t setblockstatus(EEPROM_Block_T block, EEPROM_Status_T status) { // Sets the first 64 bit section of a block to the status value. // Status is a 32-bit value, so it's doubled (copying the value) // to make it 64-bit. // Only two statuses can actually be set: Transferring and Active // Other statuses (Erased and Inactive) cannot be set. Erased can // only occur after the Flash block is physically erased. // Invalid cannot be set, it's a status for any other value that was // erroneously set. uint32_t wval; switch(status) { case EEPROM_Active: wval = 0; break; case EEPROM_Transferring: wval = FLASH_TAGGED_32B; break; default: // Error if not Active or Transferring trying to be set return EFC_RC_ERROR; break; } uint32_t addr; // Figure out the page number we are programming. if(block == EEPROM_Block0) { addr = EEPROM_BLOCK0_START; } else { addr = EEPROM_BLOCK1_START; } return write64bit(addr, wval, wval); } static uint32_t checkblockerasedandfix(EEPROM_Block_T block) { // On startup, check if a block that is labeled as erased really truly // is erased. Some weird shutdown or other error could cause a // non-erased block to appear to be erased. // This function confirms that the entire block actually is erased // by checking that every byte is 0xFF (erased Flash). If anything doesn't match // the expected value for erased Flash then the block is erased. uint32_t retval; uint32_t* block_mem; uint32_t block_erase_count = 0; uint32_t block_erase_key = 0; EEPROM_Block_T otherblock; EEPROM_Write_Status_T writestatus; if(block == EEPROM_Block0) { otherblock = EEPROM_Block1; block_mem = (uint32_t*) EEPROM_BLOCK0_START; block_erase_key = EEKEY_NumTimesBlock0Erased; } else { otherblock = EEPROM_Block0; block_mem = (uint32_t*) EEPROM_BLOCK1_START; block_erase_key = EEKEY_NumTimesBlock1Erased; } for(uint32_t i = 0; i < (BLOCK_SIZE / sizeof(uint32_t)); i++) { if(block_mem[i] != FLASH_ERASED_32B) { retval = eraseblock(block); if(retval != EFC_RC_OK) return retval; // Increment the block erasure count //if(EEPROM_Found == EEPROM_Read(block_erase_key, &block_erase_count)) { if(EEPROM_Found == readvariable(otherblock, block_erase_key, &block_erase_count)) { //writestatus = EEPROM_Write(block_erase_key, block_erase_count + 1); writestatus = writevariable(otherblock, block_erase_key, block_erase_count + 1); } else { //writestatus = EEPROM_Write(block_erase_key, 1); writestatus = writevariable(otherblock, block_erase_key, 1); } if(writestatus != EEPROM_Write_Success) return EFC_RC_ERROR; return EFC_RC_OK; } } return EFC_RC_OK; } static uint32_t formateeprom(void) { uint32_t retval; EEPROM_Write_Status_T writestatus; // Deletes both blocks. // Assigns block 0 as the active block. retval = eraseblock(EEPROM_Block0); if(retval != EFC_RC_OK) return retval; retval = eraseblock(EEPROM_Block1); if(retval != EFC_RC_OK) return retval; retval = setblockstatus(EEPROM_Block0, EEPROM_Active); if(retval != EFC_RC_OK) return retval; // Now set the erasure counts to 1 for both blocks. //writestatus = EEPROM_Write(EEKEY_NumTimesBlock0Erased, 1); writestatus = writevariable(EEPROM_Block0, EEKEY_NumTimesBlock0Erased, 1); if(writestatus != EEPROM_Write_Success) return EFC_RC_ERROR; //retval = EEPROM_Write(EEKEY_NumTimesBlock1Erased, 1); writestatus = writevariable(EEPROM_Block0, EEKEY_NumTimesBlock1Erased, 1); if(writestatus != EEPROM_Write_Success) return EFC_RC_ERROR; return EFC_RC_OK; } static uint32_t blocktransfer(EEPROM_Block_T source, EEPROM_Block_T dest) { uint32_t retval; uint32_t eeprom_dat; EEPROM_Read_Status_T readstatus; EEPROM_Write_Status_T writestatus; // Transfers all data members in the list EEPROM_Keys from "source" block to // "dest" block. // Check if dest has already been tagged as transferring. If not, tag it. if(getblockstatus(dest) != EEPROM_Transferring) { retval = setblockstatus(dest, EEPROM_Transferring); if(retval != EFC_RC_OK) return retval; } // Check for every variable in our list. If found, transfer it to the new block for(uint32_t var_id = 0; var_id < Num_EEPROM_Keys; var_id++) { readstatus = readvariable(source, EEPROM_Keys[var_id], &eeprom_dat); if(readstatus == EEPROM_Read_Error) return EFC_RC_ERROR; if(readstatus == EEPROM_Found) { writestatus = writevariable(dest, EEPROM_Keys[var_id], eeprom_dat); if(writestatus != EEPROM_Write_Success) return EFC_RC_ERROR; } } // Erase the old block retval = eraseblock(source); if(retval != EFC_RC_OK) return retval; // Set the new block as active retval = setblockstatus(dest, EEPROM_Active); if(retval != EFC_RC_OK) return retval; // Increment the erased count uint32_t block_erase_key = (source == EEPROM_Block0) ? EEKEY_NumTimesBlock0Erased : EEKEY_NumTimesBlock1Erased; uint32_t block_erase_count = 0; //if(EEPROM_Found == EEPROM_Read(block_erase_key, &block_erase_count)) { if(EEPROM_Found == readvariable(dest, block_erase_key, &block_erase_count)) { //writestatus = EEPROM_Write(block_erase_key, block_erase_count + 1); writestatus = writevariable(dest, block_erase_key, block_erase_count + 1); } else { //writestatus = EEPROM_Write(block_erase_key, 1); writestatus = writevariable(dest, block_erase_key, 1); } if(writestatus != EEPROM_Write_Success) return EFC_RC_ERROR; return EFC_RC_OK; } static EEPROM_Read_Status_T readvariable(EEPROM_Block_T block, uint32_t key, uint32_t* data) { // Since data is written from low addresses to high addresses, we should search from // high to low to find the key. That way the first key found will be the most recent // data value. uint32_t offset_addr; uint32_t* memptr; switch(block) { case EEPROM_Block0: offset_addr = EEPROM_BLOCK0_START; break; case EEPROM_Block1: offset_addr = EEPROM_BLOCK1_START; break; default: return EEPROM_Read_Error; break; } for(uint32_t addr = (BLOCK_SIZE - EEPROM_VARIABLE_SIZE); addr > 0; addr -= EEPROM_VARIABLE_SIZE) { memptr = (uint32_t*)(offset_addr + addr); if(memptr[0] == key) { *data = memptr[1]; return EEPROM_Found; } } return EEPROM_Not_Found; } static EEPROM_Write_Status_T writevariable(EEPROM_Block_T block, uint32_t key, uint32_t data) { // Find the first available spot to write the new data. Start searching from lowest // address excluding the first 8 bytes, which are the block tag. uint32_t offset_addr; uint32_t* memptr; switch(block) { case EEPROM_Block0: offset_addr = EEPROM_BLOCK0_START; break; case EEPROM_Block1: offset_addr = EEPROM_BLOCK1_START; break; default: return EEPROM_Write_Error; break; } for(uint32_t addr = EEPROM_VARIABLE_SIZE; addr < BLOCK_SIZE; addr += EEPROM_VARIABLE_SIZE) { memptr = (uint32_t*)(offset_addr + addr); if(memptr[0] == FLASH_ERASED_32B) { // This is a blank spot, now we can write the data here if(write64bit((uint32_t)memptr, key, data) == EFC_RC_OK) { return EEPROM_Write_Success; } else { return EEPROM_Write_Error; } } } // If we went all the way through the block and couldn't find a blank spot, the block is full // and it's time to transfer to the other block return EEPROM_Full; } static EEPROM_Block_T findvalidblock(EEPROM_Operation_T oper) { EEPROM_Status_T block0status, block1status; // Get block statuses block0status = getblockstatus(EEPROM_Block0); block1status = getblockstatus(EEPROM_Block1); // Write or read operation switch (oper) { case EEPROM_Write_Operation: if (block1status == EEPROM_Active) { if (block0status == EEPROM_Transferring) { return EEPROM_Block0; } else { return EEPROM_Block1; } } else if (block0status == EEPROM_Active) { if (block1status == EEPROM_Transferring) { return EEPROM_Block1; } else { return EEPROM_Block0; } } else { return EEPROM_No_Block; } case EEPROM_Read_Operation: if (block0status == EEPROM_Active) { return EEPROM_Block0; } else if (block1status == EEPROM_Active) { return EEPROM_Block1; } else { return EEPROM_No_Block; } default: return EEPROM_No_Block; } } uint32_t EEPROM_Init(void) { EEPROM_Status_T block0status, block1status; uint32_t status; EEPROM_Mutex_Handle = xSemaphoreCreateMutex(); if(NULL == EEPROM_Mutex_Handle){ return EFC_RC_ERROR; } // Get block statuses block0status = getblockstatus(EEPROM_Block0); block1status = getblockstatus(EEPROM_Block1); // Check for invalid header states and repair if necessary switch (block0status) { case EEPROM_Erased: switch(block1status) { case EEPROM_Active: // Confirm block0 is properly erased, and erase it if necessary status = checkblockerasedandfix(EEPROM_Block0); if(status != EFC_RC_OK) { return status; } break; case EEPROM_Transferring: // Possible to get to this state if block1 is done receiving, // block0 was erased, but power was lost before block1 was // tagged as active // Confirm block0 is properly erased, and erase it if necessary status = checkblockerasedandfix(EEPROM_Block0); if (status != EFC_RC_OK) { return status; } status = setblockstatus(EEPROM_Block1, EEPROM_Active); if (status != EFC_RC_OK) { return status; } break; default: // Could be erased (both pages) or invalid // in either case, format EEPROM status = formateeprom(); // If erase/program operation was failed, a Flash error code is returned if (status != EFC_RC_OK) { return status; } break; } break; case EEPROM_Transferring: switch(block1status) { case EEPROM_Active: // Transfer data from block1 to block0 status = blocktransfer(EEPROM_Block1, EEPROM_Block0); if (status != EFC_RC_OK) { return status; } break; case EEPROM_Erased: // Possible to get to this state if block0 is done receiving, // block1 was erased, but power was lost before block0 was // tagged as active // Confirm block1 is properly erased, and erase it if necessary status = checkblockerasedandfix(EEPROM_Block1); if (status != EFC_RC_OK) { return status; } status = setblockstatus(EEPROM_Block0, EEPROM_Active); if (status != EFC_RC_OK) { return status; } break; default: // Anything else is an invalid state status = formateeprom(); if (status != EFC_RC_OK) { return status; } break; } break; case EEPROM_Active: switch(block1status) { case EEPROM_Active: // Invalid state -> format eeprom // Erase both block0 and block1 and set block0 as valid page status = formateeprom(); // If erase/program operation was failed, a Flash error code is returned if (status != EFC_RC_OK) { return status; } break; case EEPROM_Erased: // block0 valid, block1 erased // Erase block1 only if it was improperly erased. status = checkblockerasedandfix(EEPROM_Block1); // If erase operation was failed, a Flash error code is returned if (status != EFC_RC_OK) { return status; } break; default: // Transfer data from block0 to block1 status = blocktransfer(EEPROM_Block0, EEPROM_Block1); if (status != EFC_RC_OK) { return status; } break; } break; default: // Any other state -> format eeprom // Erase both block0 and block1 and set block0 as valid page status = formateeprom(); // If erase/program operation was failed, a Flash error code is returned if (status != EFC_RC_OK) { return status; } break; } EEPROM_Finished_Init = true; return EFC_RC_OK; } EEPROM_Read_Status_T EEPROM_Read(uint32_t key, uint32_t* data) { EEPROM_Block_T block; EEPROM_Read_Status_T retstatus; if(!EEPROM_Finished_Init) return EEPROM_Read_Not_Init; xSemaphoreTake(EEPROM_Mutex_Handle, portMAX_DELAY); // check which block is active block = findvalidblock(EEPROM_Read_Operation); retstatus = readvariable(block, key, data); xSemaphoreGive(EEPROM_Mutex_Handle); return retstatus; } EEPROM_Write_Status_T EEPROM_Write(uint32_t key, uint32_t data) { EEPROM_Block_T block; EEPROM_Write_Status_T writestatus; uint32_t retval; if(!EEPROM_Finished_Init) return EEPROM_Write_Not_Init; xSemaphoreTake(EEPROM_Mutex_Handle, portMAX_DELAY); // check which block is active block = findvalidblock(EEPROM_Write_Operation); // Attempt to write the variable writestatus = writevariable(block, key, data); if(EEPROM_Full == writestatus) { // Try transferring blocks EEPROM_Block_T other_block = (block == EEPROM_Block0) ? EEPROM_Block1 : EEPROM_Block0; retval = blocktransfer(block, other_block); if(retval != EFC_RC_OK) { writestatus = EEPROM_Write_Error; } else { // Try writing again writestatus = writevariable(other_block, key, data); // And if at this point the other block is full, we have no chance to save this // variable anyway. Can't attempt transferring again. } } xSemaphoreGive(EEPROM_Mutex_Handle); return writestatus; } EEPROM_Block_T EEPROM_Which_Block_Active(void) { // Super simple calculation here. Just check if Block0 is active. If not // assume Block1 is. Don't need to get all complicated with edge cases here. if(getblockstatus(EEPROM_Block0) == EEPROM_Active) { return EEPROM_Block0; } return EEPROM_Block1; }