1046 lines
34 KiB
C
1046 lines
34 KiB
C
/**
|
|
* \file
|
|
*
|
|
* \brief SAM EEPROM Emulator
|
|
*
|
|
* Copyright (C) 2012-2016 Atmel Corporation. All rights reserved.
|
|
*
|
|
* \asf_license_start
|
|
*
|
|
* \page License
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* 3. The name of Atmel may not be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 4. This software may only be redistributed and used in connection with an
|
|
* Atmel microcontroller product.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
|
|
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
|
|
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* \asf_license_stop
|
|
*
|
|
*/
|
|
/*
|
|
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
|
|
*/
|
|
#include "eeprom.h"
|
|
#include <string.h>
|
|
#include <nvm.h>
|
|
|
|
/**
|
|
* \internal
|
|
* Magic key is the sequence "AtEEPROMEmu." in ASCII. The key is encoded as a
|
|
* sequence of 32-bit values to speed up checking of the key, which can be
|
|
* implemented as a number of simple integer comparisons,
|
|
*/
|
|
#define EEPROM_MAGIC_KEY {0x41744545, 0x50524f4d, 0x456d752e}
|
|
|
|
/** \internal
|
|
* Length of the magic key, in 32-bit elements.
|
|
*/
|
|
#define EEPROM_MAGIC_KEY_COUNT 3
|
|
|
|
COMPILER_PACK_SET(1);
|
|
/**
|
|
* \internal
|
|
* \brief Structure describing the EEPROM Emulation master page.
|
|
*/
|
|
struct _eeprom_master_page {
|
|
/** Magic key which in ASCII will show as AtEEPROMEmu. */
|
|
uint32_t magic_key[EEPROM_MAGIC_KEY_COUNT];
|
|
|
|
/** Emulator major version information. */
|
|
uint8_t major_version;
|
|
/** Emulator minor version information. */
|
|
uint8_t minor_version;
|
|
/** Emulator revision version information. */
|
|
uint8_t revision;
|
|
|
|
/** Emulator identification value (to distinguish between different emulator
|
|
* schemes that carry the same version numbers). */
|
|
uint8_t emulator_id;
|
|
|
|
/** Unused reserved bytes in the master page. */
|
|
uint8_t reserved[48];
|
|
};
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Structure describing emulated pages of EEPROM data.
|
|
*/
|
|
struct _eeprom_page {
|
|
/** Header information of the EEPROM page. */
|
|
struct {
|
|
uint8_t logical_page;
|
|
uint8_t reserved[EEPROM_HEADER_SIZE - 1];
|
|
} header;
|
|
|
|
/** Data content of the EEPROM page. */
|
|
uint8_t data[EEPROM_PAGE_SIZE];
|
|
};
|
|
COMPILER_PACK_RESET();
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Internal device instance struct.
|
|
*/
|
|
struct _eeprom_module {
|
|
/** Initialization state of the EEPROM emulator. */
|
|
bool initialized;
|
|
|
|
/** Absolute byte pointer to the first byte of FLASH where the emulated
|
|
* EEPROM is stored. */
|
|
const struct _eeprom_page *flash;
|
|
|
|
/** Number of physical FLASH pages occupied by the EEPROM emulator. */
|
|
uint16_t physical_pages;
|
|
/** Number of logical FLASH pages occupied by the EEPROM emulator. */
|
|
uint8_t logical_pages;
|
|
|
|
/** Mapping array from logical EEPROM pages to physical FLASH pages. */
|
|
uint8_t page_map[EEPROM_MAX_PAGES / 2 - 4];
|
|
|
|
/** Row number for the spare row (used by next write). */
|
|
uint8_t spare_row;
|
|
|
|
/** Buffer to hold the currently cached page. */
|
|
struct _eeprom_page cache;
|
|
/** Indicates if the cache contains valid data. */
|
|
bool cache_active;
|
|
};
|
|
|
|
/**
|
|
* \internal
|
|
* \brief Internal EEPROM emulator instance.
|
|
*/
|
|
static struct _eeprom_module _eeprom_instance = {
|
|
.initialized = false,
|
|
};
|
|
|
|
|
|
/** \internal
|
|
* \brief Erases a given row within the physical EEPROM memory space.
|
|
*
|
|
* \param[in] row Physical row in EEPROM space to erase
|
|
*/
|
|
static void _eeprom_emulator_nvm_erase_row(
|
|
const uint8_t row)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
|
|
do {
|
|
error_code = nvm_erase_row(
|
|
(uint32_t)&_eeprom_instance.flash[row * NVMCTRL_ROW_PAGES]);
|
|
} while (error_code == STATUS_BUSY);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Fills the internal NVM controller page buffer in physical EEPROM memory space.
|
|
*
|
|
* \param[in] physical_page Physical page in EEPROM space to fill
|
|
* \param[in] data Data to write to the physical memory page
|
|
*/
|
|
static void _eeprom_emulator_nvm_fill_cache(
|
|
const uint16_t physical_page,
|
|
const void* const data)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
|
|
do {
|
|
error_code = nvm_write_buffer(
|
|
(uint32_t)&_eeprom_instance.flash[physical_page],
|
|
(uint8_t*)data,
|
|
NVMCTRL_PAGE_SIZE);
|
|
} while (error_code == STATUS_BUSY);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Commits the internal NVM controller page buffer to physical memory.
|
|
*
|
|
* \param[in] physical_page Physical page in EEPROM space to commit
|
|
*/
|
|
static void _eeprom_emulator_nvm_commit_cache(
|
|
const uint16_t physical_page)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
|
|
do {
|
|
error_code = nvm_execute_command(
|
|
NVM_COMMAND_WRITE_PAGE,
|
|
(uint32_t)&_eeprom_instance.flash[physical_page], 0);
|
|
} while (error_code == STATUS_BUSY);
|
|
}
|
|
|
|
/** \internal
|
|
* \brief Reads a page of data stored in physical EEPROM memory space.
|
|
*
|
|
* \param[in] physical_page Physical page in EEPROM space to read
|
|
* \param[out] data Destination buffer to fill with the read data
|
|
*/
|
|
static void _eeprom_emulator_nvm_read_page(
|
|
const uint16_t physical_page,
|
|
void* const data)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
|
|
do {
|
|
error_code = nvm_read_buffer(
|
|
(uint32_t)&_eeprom_instance.flash[physical_page],
|
|
(uint8_t*)data,
|
|
NVMCTRL_PAGE_SIZE);
|
|
} while (error_code == STATUS_BUSY);
|
|
}
|
|
|
|
/**
|
|
* \brief Initializes the emulated EEPROM memory, destroying the current contents.
|
|
*/
|
|
static void _eeprom_emulator_format_memory(void)
|
|
{
|
|
uint16_t logical_page = 0;
|
|
|
|
/* Set row 0 as the spare row */
|
|
_eeprom_instance.spare_row = 0;
|
|
_eeprom_emulator_nvm_erase_row(_eeprom_instance.spare_row);
|
|
|
|
for (uint16_t physical_page = NVMCTRL_ROW_PAGES;
|
|
physical_page < _eeprom_instance.physical_pages; physical_page++) {
|
|
|
|
if (physical_page == EEPROM_MASTER_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
|
|
/* If we are at the first page in a new row, erase the entire row */
|
|
if ((physical_page % NVMCTRL_ROW_PAGES) == 0) {
|
|
_eeprom_emulator_nvm_erase_row(physical_page / NVMCTRL_ROW_PAGES);
|
|
}
|
|
|
|
/* Two logical pages are stored in each physical row; program in a
|
|
* pair of initialized but blank set of emulated EEPROM pages */
|
|
if ((physical_page % NVMCTRL_ROW_PAGES) < 2) {
|
|
/* Make a buffer to hold the initialized EEPROM page */
|
|
struct _eeprom_page data;
|
|
memset(&data, 0xFF, sizeof(data));
|
|
|
|
/* Set up the new EEPROM row's header */
|
|
data.header.logical_page = logical_page;
|
|
|
|
/* Write the page out to physical memory */
|
|
_eeprom_emulator_nvm_fill_cache(physical_page, &data);
|
|
_eeprom_emulator_nvm_commit_cache(physical_page);
|
|
|
|
/* Increment the logical EEPROM page address now that the current
|
|
* address' page has been initialized */
|
|
logical_page++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Check if a row is a full row
|
|
* because the page is a invalid page, so if two pages have data,
|
|
* it is the full row.
|
|
*
|
|
* \param[in] phy_page Physical page that in a row
|
|
*/
|
|
static bool _eeprom_emulator_is_full_row(uint16_t phy_page)
|
|
{
|
|
if((_eeprom_instance.flash[phy_page].header.logical_page
|
|
== _eeprom_instance.flash[phy_page+2].header.logical_page)
|
|
|| (_eeprom_instance.flash[phy_page+1].header.logical_page
|
|
== _eeprom_instance.flash[phy_page+2].header.logical_page )) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Erase one invalid page according to two invalid physical page
|
|
*
|
|
* \param[in] pre_phy_page One physical invalid page
|
|
* \param[in] next_phy_page Another physical invalid page
|
|
*/
|
|
static void _eeprom_emulator_erase_invalid_page(uint16_t pre_phy_page,uint16_t next_phy_page)
|
|
{
|
|
/* Erase the old/full row*/
|
|
if(_eeprom_emulator_is_full_row(pre_phy_page)) {
|
|
_eeprom_emulator_nvm_erase_row(pre_phy_page/4);
|
|
} else {
|
|
_eeprom_emulator_nvm_erase_row(next_phy_page/4);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Check if there exist rows with same logical pages due to power drop
|
|
* when writing or erasing page.
|
|
* when existed same logical page, the old(full) row will be erased.
|
|
*/
|
|
static void _eeprom_emulator_check_logical_page(void)
|
|
{
|
|
uint16_t i = 0, j = 0;
|
|
for (i = 0; i < _eeprom_instance.physical_pages; i=i+4) {
|
|
|
|
uint16_t pre_logical_page = _eeprom_instance.flash[i].header.logical_page;
|
|
if( pre_logical_page == EEPROM_INVALID_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
|
|
for (j = NVMCTRL_ROW_PAGES+i; j < _eeprom_instance.physical_pages; j=j+4) {
|
|
|
|
if (j == EEPROM_MASTER_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
uint16_t next_logical_page = _eeprom_instance.flash[j].header.logical_page;
|
|
if( next_logical_page == EEPROM_INVALID_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
|
|
if(pre_logical_page == next_logical_page) {
|
|
/* Found invalid logical page and erase it */
|
|
_eeprom_emulator_erase_invalid_page(i,j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Creates a map in SRAM to translate logical EEPROM pages to physical FLASH pages.
|
|
*/
|
|
static void _eeprom_emulator_update_page_mapping(void)
|
|
{
|
|
/* Check if exists invalid logical page */
|
|
_eeprom_emulator_check_logical_page();
|
|
|
|
/* Scan through all physical pages, to map physical and logical pages */
|
|
for (uint16_t c = 0; c < _eeprom_instance.physical_pages; c++) {
|
|
if (c == EEPROM_MASTER_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
|
|
/* Read in the logical page stored in the current physical page */
|
|
uint16_t logical_page = _eeprom_instance.flash[c].header.logical_page;
|
|
|
|
/* If the logical page number is valid, add it to the mapping */
|
|
if ((logical_page != EEPROM_INVALID_PAGE_NUMBER) &&
|
|
(logical_page < _eeprom_instance.logical_pages)) {
|
|
_eeprom_instance.page_map[logical_page] = c;
|
|
}
|
|
}
|
|
|
|
/* Use an invalid page number as the spare row until a valid one has been
|
|
* found */
|
|
_eeprom_instance.spare_row = EEPROM_INVALID_ROW_NUMBER;
|
|
|
|
/* Scan through all physical rows, to find an erased row to use as the
|
|
* spare */
|
|
for (uint16_t c = 0; c < (_eeprom_instance.physical_pages / NVMCTRL_ROW_PAGES); c++) {
|
|
bool spare_row_found = true;
|
|
|
|
/* Look through pages within the row to see if they are all erased */
|
|
for (uint8_t c2 = 0; c2 < NVMCTRL_ROW_PAGES; c2++) {
|
|
uint16_t physical_page = (c * NVMCTRL_ROW_PAGES) + c2;
|
|
|
|
if (physical_page == EEPROM_MASTER_PAGE_NUMBER) {
|
|
continue;
|
|
}
|
|
|
|
if (_eeprom_instance.flash[physical_page].header.logical_page !=
|
|
EEPROM_INVALID_PAGE_NUMBER) {
|
|
spare_row_found = false;
|
|
}
|
|
}
|
|
|
|
/* If we've now found the spare row, store it and abort the search */
|
|
if (spare_row_found == true) {
|
|
_eeprom_instance.spare_row = c;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Finds the next free page in the given row if one is available.
|
|
*
|
|
* \param[in] start_physical_page Physical FLASH page index of the row to
|
|
* search
|
|
* \param[out] free_physical_page Index of the physical FLASH page that is
|
|
* currently free (if one was found)
|
|
*
|
|
* \return Whether a free page was found in the specified row.
|
|
*
|
|
* \retval \c true If a free page was found
|
|
* \retval \c false If the specified row was full and needs an erase
|
|
*/
|
|
static bool _eeprom_emulator_is_page_free_on_row(
|
|
const uint8_t start_physical_page,
|
|
uint8_t *const free_physical_page)
|
|
{
|
|
/* Convert physical page number to a FLASH row and page within the row */
|
|
uint8_t row = (start_physical_page / NVMCTRL_ROW_PAGES);
|
|
uint8_t page_in_row = (start_physical_page % NVMCTRL_ROW_PAGES);
|
|
|
|
/* Look in the current row for a page that isn't currently used */
|
|
for (uint8_t c = page_in_row; c < NVMCTRL_ROW_PAGES; c++) {
|
|
/* Calculate the page number for the current page being examined */
|
|
uint8_t page = (row * NVMCTRL_ROW_PAGES) + c;
|
|
|
|
/* If the page is free, pass it to the caller and exit */
|
|
if (_eeprom_instance.flash[page].header.logical_page ==
|
|
EEPROM_INVALID_PAGE_NUMBER) {
|
|
*free_physical_page = page;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* No free page in the current row was found */
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* \brief Moves data from the specified logical page to the spare row.
|
|
*
|
|
* Moves the contents of the specified row into the spare row, so that the
|
|
* original row can be erased and re-used. The contents of the given logical
|
|
* page is replaced with a new buffer of data.
|
|
*
|
|
* \param[in] row_number Physical row to examine
|
|
* \param[in] logical_page Logical EEPROM page number in the row to update
|
|
* \param[in] data New data to replace the old in the logical page
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*/
|
|
static enum status_code _eeprom_emulator_move_data_to_spare(
|
|
const uint8_t row_number,
|
|
const uint8_t logical_page,
|
|
const uint8_t *const data)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
struct {
|
|
uint8_t logical_page;
|
|
uint8_t physical_page;
|
|
} page_trans[2];
|
|
|
|
const struct _eeprom_page *row_data =
|
|
(struct _eeprom_page *)&_eeprom_instance.flash[row_number * NVMCTRL_ROW_PAGES];
|
|
|
|
/* There should be two logical pages of data in each row, possibly with
|
|
* multiple revisions (right-most version is the newest). Start by assuming
|
|
* the left-most two pages contain the newest page revisions. */
|
|
page_trans[0].logical_page = row_data[0].header.logical_page;
|
|
page_trans[0].physical_page = (row_number * NVMCTRL_ROW_PAGES);
|
|
|
|
page_trans[1].logical_page = row_data[1].header.logical_page;
|
|
page_trans[1].physical_page = (row_number * NVMCTRL_ROW_PAGES) + 1;
|
|
|
|
/* Look for newer revisions of the two logical pages stored in the row */
|
|
for (uint8_t c = 0; c < 2; c++) {
|
|
/* Look through the remaining pages in the row for any newer revisions */
|
|
for (uint8_t c2 = 2; c2 < NVMCTRL_ROW_PAGES; c2++) {
|
|
if (page_trans[c].logical_page == row_data[c2].header.logical_page) {
|
|
page_trans[c].physical_page =
|
|
(row_number * NVMCTRL_ROW_PAGES) + c2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Need to move both saved logical pages stored in the same row */
|
|
for (uint8_t c = 0; c < 2; c++) {
|
|
/* Find the physical page index for the new spare row pages */
|
|
uint32_t new_page =
|
|
((_eeprom_instance.spare_row * NVMCTRL_ROW_PAGES) + c);
|
|
|
|
/* Commit any cached data to physical non-volatile memory */
|
|
eeprom_emulator_commit_page_buffer();
|
|
|
|
/* Check if we we are looking at the page the calling function wishes
|
|
* to change during the move operation */
|
|
if (logical_page == page_trans[c].logical_page) {
|
|
/* Fill out new (updated) logical page's header in the cache */
|
|
_eeprom_instance.cache.header.logical_page = logical_page;
|
|
|
|
/* Write data to SRAM cache */
|
|
memcpy(_eeprom_instance.cache.data, data, EEPROM_PAGE_SIZE);
|
|
} else {
|
|
/* Copy existing EEPROM page to cache buffer wholesale */
|
|
_eeprom_emulator_nvm_read_page(
|
|
page_trans[c].physical_page, &_eeprom_instance.cache);
|
|
}
|
|
|
|
/* Fill the physical NVM buffer with the new data so that it can be
|
|
* quickly committed in the future if needed due to a low power
|
|
* condition */
|
|
_eeprom_emulator_nvm_fill_cache(new_page, &_eeprom_instance.cache);
|
|
|
|
/* Update the page map with the new page location and indicate that
|
|
* the cache now holds new data */
|
|
_eeprom_instance.page_map[page_trans[c].logical_page] = new_page;
|
|
_eeprom_instance.cache_active = true;
|
|
}
|
|
|
|
/* Erase the row that was moved and set it as the new spare row */
|
|
_eeprom_emulator_nvm_erase_row(row_number);
|
|
|
|
/* Keep the index of the new spare row */
|
|
_eeprom_instance.spare_row = row_number;
|
|
|
|
return error_code;
|
|
}
|
|
|
|
/**
|
|
* \brief Create master emulated EEPROM management page.
|
|
*
|
|
* Creates a new master page in emulated EEPROM, giving information on the
|
|
* emulator used to store the EEPROM data.
|
|
*/
|
|
static void _eeprom_emulator_create_master_page(void)
|
|
{
|
|
const uint32_t magic_key[] = EEPROM_MAGIC_KEY;
|
|
|
|
struct _eeprom_master_page master_page;
|
|
memset(&master_page, 0xFF, sizeof(master_page));
|
|
|
|
/* Fill out the magic key header to indicate an initialized master page */
|
|
for (uint8_t c = 0; c < EEPROM_MAGIC_KEY_COUNT; c++) {
|
|
master_page.magic_key[c] = magic_key[c];
|
|
}
|
|
|
|
/* Update master header with version information of this emulator */
|
|
master_page.emulator_id = EEPROM_EMULATOR_ID;
|
|
master_page.major_version = EEPROM_MAJOR_VERSION;
|
|
master_page.minor_version = EEPROM_MINOR_VERSION;
|
|
master_page.revision = EEPROM_REVISION;
|
|
|
|
_eeprom_emulator_nvm_erase_row(
|
|
EEPROM_MASTER_PAGE_NUMBER / NVMCTRL_ROW_PAGES);
|
|
|
|
/* Write the new master page data to physical memory */
|
|
_eeprom_emulator_nvm_fill_cache(EEPROM_MASTER_PAGE_NUMBER, &master_page);
|
|
_eeprom_emulator_nvm_commit_cache(EEPROM_MASTER_PAGE_NUMBER);
|
|
}
|
|
|
|
/**
|
|
* \brief Verify the contents of a master EEPROM page.
|
|
*
|
|
* Verify the contents of a master EEPROM page to ensure that it contains the
|
|
* correct information for this version of the EEPROM emulation service.
|
|
*
|
|
* \retval STATUS_OK Given master page contents is valid
|
|
* \retval STATUS_ERR_BAD_FORMAT Master page contents was invalid
|
|
* \retval STATUS_ERR_IO Master page indicates the data is incompatible
|
|
* with this version of the EEPROM emulator
|
|
*/
|
|
static enum status_code _eeprom_emulator_verify_master_page(void)
|
|
{
|
|
const uint32_t magic_key[] = EEPROM_MAGIC_KEY;
|
|
struct _eeprom_master_page master_page;
|
|
|
|
/* Copy the master page to the RAM buffer so that it can be inspected */
|
|
_eeprom_emulator_nvm_read_page(EEPROM_MASTER_PAGE_NUMBER, &master_page);
|
|
|
|
/* Verify magic key is correct in the master page header */
|
|
for (uint8_t c = 0; c < EEPROM_MAGIC_KEY_COUNT; c++) {
|
|
if (master_page.magic_key[c] != magic_key[c]) {
|
|
return STATUS_ERR_BAD_FORMAT;
|
|
}
|
|
}
|
|
|
|
/* Verify emulator ID in header to ensure the same scheme is used */
|
|
if (master_page.emulator_id != EEPROM_EMULATOR_ID) {
|
|
return STATUS_ERR_IO;
|
|
}
|
|
|
|
/* Verify major version in header to ensure the same version is used */
|
|
if (master_page.major_version != EEPROM_MAJOR_VERSION) {
|
|
return STATUS_ERR_IO;
|
|
}
|
|
|
|
/* Verify minor version in header to ensure the same version is used */
|
|
if (master_page.minor_version != EEPROM_MINOR_VERSION) {
|
|
return STATUS_ERR_IO;
|
|
}
|
|
|
|
/* Don't verify revision number - same major/minor is considered enough
|
|
* to ensure the stored data is compatible. */
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Retrieves the parameters of the EEPROM Emulator memory layout.
|
|
*
|
|
* Retrieves the configuration parameters of the EEPROM Emulator, after it has
|
|
* been initialized.
|
|
*
|
|
* \param[out] parameters EEPROM Emulator parameter struct to fill
|
|
*
|
|
* \return Status of the operation.
|
|
*
|
|
* \retval STATUS_OK If the emulator parameters were retrieved
|
|
* successfully
|
|
* \retval STATUS_ERR_NOT_INITIALIZED If the EEPROM Emulator is not initialized
|
|
*/
|
|
enum status_code eeprom_emulator_get_parameters(
|
|
struct eeprom_emulator_parameters *const parameters)
|
|
{
|
|
if (_eeprom_instance.initialized == false) {
|
|
return STATUS_ERR_NOT_INITIALIZED;
|
|
}
|
|
|
|
parameters->page_size = EEPROM_PAGE_SIZE;
|
|
parameters->eeprom_number_of_pages = _eeprom_instance.logical_pages;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Initializes the EEPROM Emulator service.
|
|
*
|
|
* Initializes the emulated EEPROM memory space; if the emulated EEPROM memory
|
|
* has not been previously initialized, it will need to be explicitly formatted
|
|
* via \ref eeprom_emulator_erase_memory(). The EEPROM memory space will \b not
|
|
* be automatically erased by the initialization function, so that partial data
|
|
* may be recovered by the user application manually if the service is unable to
|
|
* initialize successfully.
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*
|
|
* \retval STATUS_OK EEPROM emulation service was successfully
|
|
* initialized
|
|
* \retval STATUS_ERR_NO_MEMORY No EEPROM section has been allocated in the
|
|
* device
|
|
* \retval STATUS_ERR_BAD_FORMAT Emulated EEPROM memory is corrupt or not
|
|
* formatted
|
|
* \retval STATUS_ERR_IO EEPROM data is incompatible with this version
|
|
* or scheme of the EEPROM emulator
|
|
*/
|
|
enum status_code eeprom_emulator_init(void)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
struct nvm_config config;
|
|
struct nvm_parameters parameters;
|
|
|
|
/* Retrieve the NVM controller configuration - enable manual page writing
|
|
* mode so that the emulator has exclusive control over page writes to
|
|
* allow for caching */
|
|
nvm_get_config_defaults(&config);
|
|
config.manual_page_write = true;
|
|
|
|
/* Apply new NVM configuration */
|
|
do {
|
|
error_code = nvm_set_config(&config);
|
|
} while (error_code == STATUS_BUSY);
|
|
|
|
/* Get the NVM controller configuration parameters */
|
|
nvm_get_parameters(¶meters);
|
|
|
|
/* Ensure the device fuses are configured for at least one master page row,
|
|
* one user EEPROM data row and one spare row */
|
|
if (parameters.eeprom_number_of_pages < (3 * NVMCTRL_ROW_PAGES)) {
|
|
return STATUS_ERR_NO_MEMORY;
|
|
}
|
|
|
|
/* Configure the EEPROM instance physical and logical number of pages:
|
|
* - One row is reserved for the master page
|
|
* - One row is reserved for the spare row
|
|
* - Two logical pages can be stored in one physical row
|
|
*/
|
|
_eeprom_instance.physical_pages =
|
|
parameters.eeprom_number_of_pages;
|
|
_eeprom_instance.logical_pages =
|
|
(parameters.eeprom_number_of_pages - (2 * NVMCTRL_ROW_PAGES)) / 2;
|
|
|
|
/* Configure the EEPROM instance starting physical address in FLASH and
|
|
* pre-compute the index of the first page in FLASH used for EEPROM */
|
|
_eeprom_instance.flash =
|
|
(void*)(FLASH_SIZE -
|
|
((uint32_t)_eeprom_instance.physical_pages * NVMCTRL_PAGE_SIZE));
|
|
|
|
/* Clear EEPROM page write cache on initialization */
|
|
_eeprom_instance.cache_active = false;
|
|
|
|
/* Scan physical memory and re-create logical to physical page mapping
|
|
* table to locate logical pages of EEPROM data in physical FLASH */
|
|
_eeprom_emulator_update_page_mapping();
|
|
|
|
/* Could not find spare row - abort as the memory appears to be corrupt */
|
|
if (_eeprom_instance.spare_row == EEPROM_INVALID_ROW_NUMBER) {
|
|
return STATUS_ERR_BAD_FORMAT;
|
|
}
|
|
|
|
/* Verify that the master page contains valid data for this service */
|
|
error_code = _eeprom_emulator_verify_master_page();
|
|
if (error_code != STATUS_OK) {
|
|
return error_code;
|
|
}
|
|
|
|
/* Mark initialization as complete */
|
|
_eeprom_instance.initialized = true;
|
|
|
|
return error_code;
|
|
}
|
|
|
|
/**
|
|
* \brief Erases the entire emulated EEPROM memory space.
|
|
*
|
|
* Erases and re-initializes the emulated EEPROM memory space, destroying any
|
|
* existing data.
|
|
*/
|
|
void eeprom_emulator_erase_memory(void)
|
|
{
|
|
/* Create new EEPROM memory block in EEPROM emulation section */
|
|
_eeprom_emulator_format_memory();
|
|
|
|
/* Write EEPROM emulation master block */
|
|
_eeprom_emulator_create_master_page();
|
|
|
|
/* Map the newly created EEPROM memory block */
|
|
_eeprom_emulator_update_page_mapping();
|
|
}
|
|
|
|
/**
|
|
* \brief Writes a page of data to an emulated EEPROM memory page.
|
|
*
|
|
* Writes an emulated EEPROM page of data to the emulated EEPROM memory space.
|
|
*
|
|
* \note Data stored in pages may be cached in volatile RAM memory; to commit
|
|
* any cached data to physical non-volatile memory, the
|
|
* \ref eeprom_emulator_commit_page_buffer() function should be called.
|
|
*
|
|
* \param[in] logical_page Logical EEPROM page number to write to
|
|
* \param[in] data Pointer to the data buffer containing source data to
|
|
* write
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*
|
|
* \retval STATUS_OK If the page was successfully read
|
|
* \retval STATUS_ERR_NOT_INITIALIZED If the EEPROM emulator is not initialized
|
|
* \retval STATUS_ERR_BAD_ADDRESS If an address outside the valid emulated
|
|
* EEPROM memory space was supplied
|
|
*/
|
|
enum status_code eeprom_emulator_write_page(
|
|
const uint8_t logical_page,
|
|
const uint8_t *const data)
|
|
{
|
|
/* Ensure the emulated EEPROM has been initialized first */
|
|
if (_eeprom_instance.initialized == false) {
|
|
return STATUS_ERR_NOT_INITIALIZED;
|
|
}
|
|
|
|
/* Make sure the write address is within the allowable address space */
|
|
if (logical_page >= _eeprom_instance.logical_pages) {
|
|
return STATUS_ERR_BAD_ADDRESS;
|
|
}
|
|
|
|
/* Check if the cache is active and the currently cached page is not the
|
|
* page that is being written (if not, we need to commit and cache the new
|
|
* page) */
|
|
if ((_eeprom_instance.cache_active == true) &&
|
|
(_eeprom_instance.cache.header.logical_page != logical_page)) {
|
|
/* Commit the currently cached data buffer to non-volatile memory */
|
|
eeprom_emulator_commit_page_buffer();
|
|
}
|
|
|
|
/* Check if we have space in the current page location's physical row for
|
|
* a new version, and if so get the new page index */
|
|
uint8_t new_page = 0;
|
|
bool page_spare = _eeprom_emulator_is_page_free_on_row(
|
|
_eeprom_instance.page_map[logical_page], &new_page);
|
|
|
|
/* Check if the current row is full, and we need to swap it out with a
|
|
* spare row */
|
|
if (page_spare == false) {
|
|
/* Move the other page we aren't writing that is stored in the same
|
|
* page to the new row, and replace the old current page with the
|
|
* new page contents (cache is updated to match) */
|
|
_eeprom_emulator_move_data_to_spare(
|
|
_eeprom_instance.page_map[logical_page] / NVMCTRL_ROW_PAGES,
|
|
logical_page,
|
|
data);
|
|
|
|
/* New data is now written and the cache is updated, exit */
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/* Update the page cache header section with the new page header */
|
|
_eeprom_instance.cache.header.logical_page = logical_page;
|
|
|
|
/* Update the page cache contents with the new data */
|
|
memcpy(&_eeprom_instance.cache.data,
|
|
data,
|
|
EEPROM_PAGE_SIZE);
|
|
|
|
/* Fill the physical NVM buffer with the new data so that it can be quickly
|
|
* committed in the future if needed due to a low power condition */
|
|
_eeprom_emulator_nvm_fill_cache(new_page, &_eeprom_instance.cache);
|
|
|
|
/* Update the cache parameters and mark the cache as active */
|
|
_eeprom_instance.page_map[logical_page] = new_page;
|
|
barrier(); // Enforce ordering to prevent incorrect cache state
|
|
_eeprom_instance.cache_active = true;
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Reads a page of data from an emulated EEPROM memory page.
|
|
*
|
|
* Reads an emulated EEPROM page of data from the emulated EEPROM memory space.
|
|
*
|
|
* \param[in] logical_page Logical EEPROM page number to read from
|
|
* \param[out] data Pointer to the destination data buffer to fill
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*
|
|
* \retval STATUS_OK If the page was successfully read
|
|
* \retval STATUS_ERR_NOT_INITIALIZED If the EEPROM emulator is not initialized
|
|
* \retval STATUS_ERR_BAD_ADDRESS If an address outside the valid emulated
|
|
* EEPROM memory space was supplied
|
|
*/
|
|
enum status_code eeprom_emulator_read_page(
|
|
const uint8_t logical_page,
|
|
uint8_t *const data)
|
|
{
|
|
/* Ensure the emulated EEPROM has been initialized first */
|
|
if (_eeprom_instance.initialized == false) {
|
|
return STATUS_ERR_NOT_INITIALIZED;
|
|
}
|
|
|
|
/* Make sure the read address is within the allowable address space */
|
|
if (logical_page >= _eeprom_instance.logical_pages) {
|
|
return STATUS_ERR_BAD_ADDRESS;
|
|
}
|
|
|
|
/* Check if the page to read is currently cached (and potentially out of
|
|
* sync/newer than the physical memory) */
|
|
if ((_eeprom_instance.cache_active == true) &&
|
|
(_eeprom_instance.cache.header.logical_page == logical_page)) {
|
|
/* Copy the potentially newer cached data into the user buffer */
|
|
memcpy(data, _eeprom_instance.cache.data, EEPROM_PAGE_SIZE);
|
|
} else {
|
|
struct _eeprom_page temp;
|
|
|
|
/* Copy the data from non-volatile memory into the temporary buffer */
|
|
_eeprom_emulator_nvm_read_page(
|
|
_eeprom_instance.page_map[logical_page], &temp);
|
|
|
|
/* Copy the data portion of the read page to the user's buffer */
|
|
memcpy(data, temp.data, EEPROM_PAGE_SIZE);
|
|
}
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief Writes a buffer of data to the emulated EEPROM memory space.
|
|
*
|
|
* Writes a buffer of data to a section of emulated EEPROM memory space. The
|
|
* source buffer may be of any size, and the destination may lie outside of an
|
|
* emulated EEPROM page boundary.
|
|
*
|
|
* \note Data stored in pages may be cached in volatile RAM memory; to commit
|
|
* any cached data to physical non-volatile memory, the
|
|
* \ref eeprom_emulator_commit_page_buffer() function should be called.
|
|
*
|
|
* \param[in] offset Starting byte offset to write to, in emulated EEPROM
|
|
* memory space
|
|
* \param[in] data Pointer to the data buffer containing source data to write
|
|
* \param[in] length Length of the data to write, in bytes
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*
|
|
* \retval STATUS_OK If the page was successfully read
|
|
* \retval STATUS_ERR_NOT_INITIALIZED If the EEPROM emulator is not initialized
|
|
* \retval STATUS_ERR_BAD_ADDRESS If an address outside the valid emulated
|
|
* EEPROM memory space was supplied
|
|
*/
|
|
enum status_code eeprom_emulator_write_buffer(
|
|
const uint16_t offset,
|
|
const uint8_t *const data,
|
|
const uint16_t length)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
uint8_t buffer[EEPROM_PAGE_SIZE];
|
|
uint8_t logical_page = offset / EEPROM_PAGE_SIZE;
|
|
uint16_t c = offset;
|
|
/* Keep track of whether the currently updated page has been written */
|
|
bool page_dirty = false;
|
|
/** Perform the initial page read if necessary*/
|
|
if ((offset % EEPROM_PAGE_SIZE) || length < EEPROM_PAGE_SIZE) {
|
|
error_code = eeprom_emulator_read_page(logical_page, buffer);
|
|
|
|
if (error_code != STATUS_OK) {
|
|
return error_code;
|
|
}
|
|
}
|
|
|
|
/* To avoid entering into the initial if in the loop the first time */
|
|
if ((offset % EEPROM_PAGE_SIZE) == 0) {
|
|
buffer[c % EEPROM_PAGE_SIZE] = data[c - offset];
|
|
page_dirty = true;
|
|
c=c+1;
|
|
}
|
|
|
|
/* Write the specified data to the emulated EEPROM memory space */
|
|
for (; c < (length + offset); c++) {
|
|
/* Check if we have written up to a new EEPROM page boundary */
|
|
if ((c % EEPROM_PAGE_SIZE) == 0) {
|
|
/* Write the current page to non-volatile memory from the temporary
|
|
* buffer */
|
|
error_code = eeprom_emulator_write_page(logical_page, buffer);
|
|
page_dirty = false;
|
|
|
|
if (error_code != STATUS_OK) {
|
|
break;
|
|
}
|
|
|
|
/* Increment the page number we are looking at */
|
|
logical_page++;
|
|
|
|
/* Read the next page from non-volatile memory into the temporary
|
|
* buffer in case of a partial page write */
|
|
error_code = eeprom_emulator_read_page(logical_page, buffer);
|
|
|
|
if (error_code != STATUS_OK) {
|
|
return error_code;
|
|
}
|
|
}
|
|
/* Copy the next byte of data from the user's buffer to the temporary
|
|
* buffer */
|
|
buffer[c % EEPROM_PAGE_SIZE] = data[c - offset];
|
|
page_dirty = true;
|
|
}
|
|
|
|
/* If the current page is dirty, write it */
|
|
if (page_dirty) {
|
|
error_code = eeprom_emulator_write_page(logical_page, buffer);
|
|
}
|
|
|
|
return error_code;
|
|
}
|
|
|
|
/**
|
|
* \brief Reads a buffer of data from the emulated EEPROM memory space.
|
|
*
|
|
* Reads a buffer of data from a section of emulated EEPROM memory space. The
|
|
* destination buffer may be of any size, and the source may lie outside of an
|
|
* emulated EEPROM page boundary.
|
|
*
|
|
* \param[in] offset Starting byte offset to read from, in emulated EEPROM
|
|
* memory space
|
|
* \param[out] data Pointer to the data buffer containing source data to read
|
|
* \param[in] length Length of the data to read, in bytes
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*
|
|
* \retval STATUS_OK If the page was successfully read
|
|
* \retval STATUS_ERR_NOT_INITIALIZED If the EEPROM emulator is not initialized
|
|
* \retval STATUS_ERR_BAD_ADDRESS If an address outside the valid emulated
|
|
* EEPROM memory space was supplied
|
|
*/
|
|
enum status_code eeprom_emulator_read_buffer(
|
|
const uint16_t offset,
|
|
uint8_t *const data,
|
|
const uint16_t length)
|
|
{
|
|
enum status_code error_code;
|
|
uint8_t buffer[EEPROM_PAGE_SIZE];
|
|
uint8_t logical_page = offset / EEPROM_PAGE_SIZE;
|
|
uint16_t c = offset;
|
|
|
|
/** Perform the initial page read */
|
|
error_code = eeprom_emulator_read_page(logical_page, buffer);
|
|
if (error_code != STATUS_OK) {
|
|
return error_code;
|
|
}
|
|
|
|
/* To avoid entering into the initial if in the loop the first time */
|
|
if ((offset % EEPROM_PAGE_SIZE) == 0) {
|
|
data[0] = buffer[0];
|
|
c=c+1;
|
|
}
|
|
|
|
/* Read in the specified data from the emulated EEPROM memory space */
|
|
for (; c < (length + offset); c++) {
|
|
/* Check if we have read up to a new EEPROM page boundary */
|
|
if ((c % EEPROM_PAGE_SIZE) == 0) {
|
|
/* Increment the page number we are looking at */
|
|
logical_page++;
|
|
|
|
/* Read the next page from non-volatile memory into the temporary
|
|
* buffer */
|
|
error_code = eeprom_emulator_read_page(logical_page, buffer);
|
|
|
|
if (error_code != STATUS_OK) {
|
|
return error_code;
|
|
}
|
|
}
|
|
|
|
/* Copy the next byte of data from the temporary buffer to the user's
|
|
* buffer */
|
|
data[c - offset] = buffer[c % EEPROM_PAGE_SIZE];
|
|
}
|
|
|
|
return error_code;
|
|
}
|
|
|
|
/**
|
|
* \brief Commits any cached data to physical non-volatile memory.
|
|
*
|
|
* Commits the internal SRAM caches to physical non-volatile memory, to ensure
|
|
* that any outstanding cached data is preserved. This function should be called
|
|
* prior to a system reset or shutdown to prevent data loss.
|
|
*
|
|
* \note This should be the first function executed in a BOD33 Early Warning
|
|
* callback to ensure that any outstanding cache data is fully written to
|
|
* prevent data loss.
|
|
*
|
|
*
|
|
* \note This function should also be called before using the NVM controller
|
|
* directly in the user-application for any other purposes to prevent
|
|
* data loss.
|
|
*
|
|
* \return Status code indicating the status of the operation.
|
|
*/
|
|
enum status_code eeprom_emulator_commit_page_buffer(void)
|
|
{
|
|
enum status_code error_code = STATUS_OK;
|
|
|
|
/* If cache is inactive, no need to commit anything to physical memory */
|
|
if (_eeprom_instance.cache_active == false) {
|
|
return STATUS_OK;
|
|
}
|
|
|
|
uint8_t cached_logical_page = _eeprom_instance.cache.header.logical_page;
|
|
|
|
/* Perform the page write to commit the NVM page buffer to FLASH */
|
|
_eeprom_emulator_nvm_commit_cache(
|
|
_eeprom_instance.page_map[cached_logical_page]);
|
|
|
|
barrier(); // Enforce ordering to prevent incorrect cache state
|
|
_eeprom_instance.cache_active = false;
|
|
|
|
return error_code;
|
|
}
|