Files
MySensors/drivers/NVM/VirtualPage.cpp
T
d00616 726a20dad9 Add nrf5 platform (#830)
* 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.
2017-05-14 11:53:01 +02:00

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;
}