Files
MySensors/drivers/NVM/VirtualPage.cpp

342 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.top_app_page_address() -
((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])+1) &
(uint32_t)MASK_ERASE_COUNTER);
}