* Add NVRAM support This is an EEPROM replacement based on MCU's internal Flash memory. The NVRAM class is designed to be stateless. Each operation is written to flash immediately. The last 16k(nRF51)/32k(nRF52) of Flash memory are managed by the VirtualPage class. This class allows writing a single emulated 4k page up 40,000 times. The NVRAM class is a log based byte-wise storage allowing up to 40,000,000 writes depending on the number of used addresses. Each four allocated addresses from the beginning of the NVRAM reduce the write log by one. When first 256 bytes of the emulated storage are used, there is an average writing capacity of 145,000 cycles per byte. When the write log is full, the NVRAM is compressed into a new flash page. After a new page is ready, the old page is released. This process can take up to 4500 ms and cannot be interrupted. The NVRAM functionality can easily be ported to ESP8266 or SAMD by providing a Flash interface. * Add NRF5 platform support This is the support of the Nordic Semiconductor NRF51 (nrf51822, nrf51422) and NRF52 (nrf52832) platform. Based on arduino-nRF5 (https://github.com/sandeepmistry/arduino-nRF5). The included radio driver is compatible with the NRF24 devices. Using a SoftDevice (BLE)is not supported and must be removed before flashing MySensors with a "mass_erase" operation.
343 lines
9.7 KiB
C++
343 lines
9.7 KiB
C++
/*
|
|
VirtualPage.cpp - Flash page management
|
|
Original Copyright (c) 2017 Frank Holtz. All right reserved.
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "VirtualPage.h"
|
|
|
|
VirtualPageClass VirtualPage;
|
|
|
|
#ifndef VNM_VIRTUAL_PAGE_SIZE_BITS
|
|
#define VNM_VIRTUAL_PAGE_SIZE_BITS 12
|
|
#elif VNM_VIRTUAL_PAGE_SIZE_BITS < 12
|
|
#error "VNM_VIRTUAL_PAGE_SIZE_BITS must be >= 12"
|
|
#endif
|
|
|
|
// check page size
|
|
#ifndef VNM_VIRTUAL_PAGE_COUNT
|
|
#if FLASH_ERASE_CYCLES >= 20000
|
|
// use 16k of flash memory
|
|
#define VNM_VIRTUAL_PAGE_COUNT 4
|
|
#else
|
|
// use 32k of flash memory
|
|
#define VNM_VIRTUAL_PAGE_COUNT 8
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
* How many virtual pages are skipped from top of flash
|
|
*/
|
|
#ifndef VNM_VIRTUAL_PAGE_SKIP_FROM_TOP
|
|
#define VNM_VIRTUAL_PAGE_SKIP_FROM_TOP 0
|
|
#endif
|
|
|
|
/*
|
|
* Calculate things around VNM_VIRTUAL_PAGE_SIZE
|
|
*/
|
|
#define VNM_VIRTUAL_PAGE_SIZE (1 << (VNM_VIRTUAL_PAGE_SIZE_BITS))
|
|
#define VNM_VIRTUAL_PAGE_ADDRESS_MASK (~(VNM_VIRTUAL_PAGE_SIZE - 1))
|
|
#define VNM_VIRTUAL_PAGE_ALIGN(address) \
|
|
{ address = (uint32_t *)((uint32_t)address & VNM_VIRTUAL_PAGE_ADDRESS_MASK); }
|
|
|
|
/*
|
|
* Defines the position of status words in a page.
|
|
* Offsets are defined in words!
|
|
*/
|
|
#ifdef FLASH_SUPPORTS_RANDOM_WRITE
|
|
// use first 8 byte for magic, erase counter and status
|
|
#define OFFSET_MAGIC 0
|
|
#define OFFSET_ERASE_COUNTER 1
|
|
#if FLASH_WRITES_PER_WORD > 2
|
|
// use first 12 bytes for magic, erase counter and status
|
|
#define MASK_ERASE_COUNTER 0x00FFFFFF
|
|
#define OFFSET_STATUS_RELEASE_PREPARE 1
|
|
#define OFFSET_STATUS_RELEASE_END 1
|
|
#define METADATA_SIZE 8
|
|
#define OFFSET_DATA 2
|
|
#elif FLASH_WRITES_PER_WORD == 2
|
|
#define MASK_ERASE_COUNTER 0x00FFFFFF
|
|
#define OFFSET_STATUS_RELEASE_PREPARE 2
|
|
#define OFFSET_STATUS_RELEASE_END 2
|
|
#define METADATA_SIZE 12
|
|
#define OFFSET_DATA 3
|
|
#else
|
|
// use first 12 bytes for erase counter, and magic
|
|
#define OFFSET_MAGIC 1
|
|
#define OFFSET_COUNTER 0
|
|
#define MASK_ERASE_COUNTER 0x00FFFFFF
|
|
#define OFFSET_STATUS_RELEASE_PREPARE VNM_VIRTUAL_PAGE_SIZE - 8
|
|
#define OFFSET_STATUS_RELEASE_END VNM_VIRTUAL_PAGE_SIZE - 4
|
|
#define METADATA_SIZE 16
|
|
#define OFFSET_DATA 4
|
|
#endif
|
|
|
|
#define BIT_STATUS_RELEASE_PREPARE (1 << 30)
|
|
#define BIT_STATUS_RELEASE_END (1 << 31)
|
|
|
|
#define VNM_VIRTUAL_PAGE_DATA_SIZE (VNM_VIRTUAL_PAGE_SIZE - METADATA_SIZE)
|
|
#else
|
|
// use first 8 byte for magic and erase counter and last 8 byte for page release
|
|
#define OFFSET_MAGIC 1
|
|
#define OFFSET_ERASE_COUNTER 0
|
|
#define OFFSET_DATA 2
|
|
#define OFFSET_STATUS_RELEASE_PREPARE \
|
|
((VNM_VIRTUAL_PAGE_SIZE - 8) / sizeof(uint32_t))
|
|
#define OFFSET_STATUS_RELEASE_END \
|
|
((VNM_VIRTUAL_PAGE_SIZE - 4) / sizeof(uint32_t))
|
|
|
|
#define MASK_ERASE_COUNTER 0xFFFFFFFF
|
|
|
|
#define BIT_STATUS_RELEASE_PREPARE 1
|
|
#define BIT_STATUS_RELEASE_END 1
|
|
|
|
#define VNM_VIRTUAL_PAGE_DATA_SIZE (VNM_VIRTUAL_PAGE_SIZE - 16)
|
|
|
|
#endif
|
|
|
|
uint16_t VirtualPageClass::size() const
|
|
{
|
|
return (VNM_VIRTUAL_PAGE_DATA_SIZE);
|
|
}
|
|
|
|
uint16_t VirtualPageClass::length() const
|
|
{
|
|
return (VNM_VIRTUAL_PAGE_DATA_SIZE / 4);
|
|
}
|
|
|
|
uint16_t VirtualPageClass::page_count() const
|
|
{
|
|
return (VNM_VIRTUAL_PAGE_COUNT - 1);
|
|
}
|
|
|
|
uint32_t VirtualPageClass::wear_level()
|
|
{
|
|
uint32_t max_erase_cycles = 0;
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t erase_cycles = get_page_erase_cycles(get_page_address(i));
|
|
if (erase_cycles > max_erase_cycles) {
|
|
max_erase_cycles = erase_cycles;
|
|
}
|
|
}
|
|
return (uint32_t)((((uint64_t)max_erase_cycles * 10000)) /
|
|
Flash.specified_erase_cycles());
|
|
}
|
|
|
|
uint32_t *VirtualPageClass::get(uint32_t magic)
|
|
{
|
|
|
|
// Give back a page prepared for release and not closed
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t *page = get_page_address(i);
|
|
if (
|
|
// correct magic is set
|
|
(page[OFFSET_MAGIC] == magic) &&
|
|
// page is in release_prepare mode
|
|
((page[OFFSET_STATUS_RELEASE_PREPARE] & BIT_STATUS_RELEASE_PREPARE) ==
|
|
0) &&
|
|
// page is not released
|
|
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) {
|
|
// Return page in release process with priority
|
|
return &page[OFFSET_DATA];
|
|
}
|
|
}
|
|
|
|
// check if a unreleased page is available
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t *page = get_page_address(i);
|
|
if (
|
|
// correct magic is set
|
|
(page[OFFSET_MAGIC] == magic) &&
|
|
// page is not released
|
|
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) {
|
|
// return page in normal operation
|
|
return &page[OFFSET_DATA];
|
|
}
|
|
}
|
|
|
|
return (uint32_t *)(~0);
|
|
}
|
|
|
|
uint32_t *VirtualPageClass::allocate(uint32_t magic)
|
|
{
|
|
uint32_t *return_page = (uint32_t *)(~0);
|
|
uint32_t max_erase_cycles = (uint32_t)~0;
|
|
|
|
// Avoid duplicate allocation of pages, look for the less used page
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t *page = get_page_address(i);
|
|
|
|
// Delete duplicated pages
|
|
if (
|
|
// same magic
|
|
(page[OFFSET_MAGIC] == magic) &&
|
|
// Not in release_end state
|
|
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) &&
|
|
// Not in release_prepare state
|
|
(!release_started(page))) {
|
|
// clear the page
|
|
build_page(page, (uint32_t)~0);
|
|
}
|
|
|
|
uint32_t erase_cycles = get_page_erase_cycles(page);
|
|
// When the page has less erase cycles and is not marked as failed
|
|
if ((erase_cycles < max_erase_cycles) && (page[OFFSET_MAGIC] > 0) &&
|
|
(
|
|
// magic is empty
|
|
(page[OFFSET_MAGIC] == (uint32_t)~0) ||
|
|
// marked as released
|
|
((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) ==
|
|
0))) {
|
|
max_erase_cycles = erase_cycles;
|
|
return_page = page;
|
|
}
|
|
}
|
|
|
|
// return if no page was found
|
|
if (return_page == (uint32_t *)~0) {
|
|
return return_page;
|
|
}
|
|
|
|
build_page(return_page, magic);
|
|
return &return_page[OFFSET_DATA];
|
|
}
|
|
|
|
uint32_t *VirtualPageClass::allocate(uint32_t magic, uint32_t max_writes)
|
|
{
|
|
// max_writes is not implemented yet -> page is erased with every allocate
|
|
(void)max_writes;
|
|
return allocate(magic);
|
|
}
|
|
|
|
void VirtualPageClass::release_prepare(uint32_t *address)
|
|
{
|
|
// move pointer to beginning of the page
|
|
VNM_VIRTUAL_PAGE_ALIGN(address);
|
|
|
|
// Nothing to do at a empty page
|
|
if (address[OFFSET_MAGIC] == (uint32_t)~0) {
|
|
return;
|
|
}
|
|
|
|
if (release_started(address) == false) {
|
|
// Clear bit BIT_PAGE_RELEASED
|
|
Flash.write(&address[OFFSET_STATUS_RELEASE_PREPARE],
|
|
address[OFFSET_STATUS_RELEASE_PREPARE] &
|
|
~BIT_STATUS_RELEASE_PREPARE);
|
|
}
|
|
return;
|
|
}
|
|
|
|
void VirtualPageClass::release(uint32_t *address)
|
|
{
|
|
// move pointer to beginning of the page
|
|
VNM_VIRTUAL_PAGE_ALIGN(address);
|
|
|
|
// Nothing to do at a empty page
|
|
if (address[OFFSET_MAGIC] == (uint32_t)~0) {
|
|
return;
|
|
}
|
|
|
|
// Check if status bit already cleared
|
|
if ((address[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) {
|
|
// Clear bit BIT_PAGE_RELEASED
|
|
Flash.write(&address[OFFSET_STATUS_RELEASE_END],
|
|
address[OFFSET_STATUS_RELEASE_END] & ~BIT_STATUS_RELEASE_END);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool VirtualPageClass::release_started(uint32_t *address)
|
|
{
|
|
// move pointer to beginning of the page
|
|
VNM_VIRTUAL_PAGE_ALIGN(address);
|
|
|
|
return (address[OFFSET_STATUS_RELEASE_PREPARE] &
|
|
BIT_STATUS_RELEASE_PREPARE) == 0;
|
|
}
|
|
|
|
void VirtualPageClass::fail(uint32_t *address)
|
|
{
|
|
// move pointer to beginning of the page
|
|
VNM_VIRTUAL_PAGE_ALIGN(address);
|
|
|
|
build_page(address, 0x00000000);
|
|
return;
|
|
}
|
|
|
|
void VirtualPageClass::clean_up()
|
|
{
|
|
// No page found -> try to give back a page prepared for release
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t *page = get_page_address(i);
|
|
if ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) == 0) {
|
|
build_page(get_page_address(i), ~0);
|
|
return; // a maximum of a page is cleaned -> return
|
|
}
|
|
}
|
|
}
|
|
|
|
void VirtualPageClass::format()
|
|
{
|
|
for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) {
|
|
uint32_t *address = get_page_address(i);
|
|
build_page(address, (uint32_t)~0);
|
|
}
|
|
}
|
|
|
|
uint32_t *VirtualPageClass::get_page_address(uint16_t page)
|
|
{
|
|
return (uint32_t *)((Flash.page_count() << Flash.page_size_bits()) -
|
|
((page + VNM_VIRTUAL_PAGE_SKIP_FROM_TOP)
|
|
<< VNM_VIRTUAL_PAGE_SIZE_BITS));
|
|
}
|
|
|
|
void VirtualPageClass::build_page(uint32_t *address, uint32_t magic)
|
|
{
|
|
// move pointer to beginning of the page
|
|
VNM_VIRTUAL_PAGE_ALIGN(address);
|
|
// get erase counter
|
|
uint32_t erase_counter = get_page_erase_cycles(address);
|
|
|
|
// Check if a magic is set
|
|
if (address[OFFSET_MAGIC] != (uint32_t)~0) {
|
|
Flash.erase(address, VNM_VIRTUAL_PAGE_SIZE);
|
|
} else {
|
|
// check if page is empty
|
|
for (int i = OFFSET_DATA; i < (VNM_VIRTUAL_PAGE_SIZE / 4); i++) {
|
|
if (address[i] != (uint32_t)~0) {
|
|
Flash.erase(address, VNM_VIRTUAL_PAGE_SIZE);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// write a new page
|
|
Flash.write(&address[OFFSET_MAGIC], magic);
|
|
if (address[OFFSET_ERASE_COUNTER] == (uint32_t)~0) {
|
|
Flash.write(&address[OFFSET_ERASE_COUNTER],
|
|
erase_counter | ~MASK_ERASE_COUNTER);
|
|
}
|
|
}
|
|
|
|
uint32_t VirtualPageClass::get_page_erase_cycles(uint32_t *address)
|
|
{
|
|
// Return number of cycles
|
|
return ((uint32_t)address[OFFSET_ERASE_COUNTER] &
|
|
(uint32_t)MASK_ERASE_COUNTER) +
|
|
1;
|
|
}
|