mirror of
https://github.com/IoTManagerProject/IoTManager.git
synced 2026-03-27 14:42:18 +03:00
343 lines
8.2 KiB
C++
343 lines
8.2 KiB
C++
/*
|
|
NVRAM.cpp - Byte wise storage for Virtual Pages.
|
|
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 "NVRAM.h"
|
|
|
|
// VirtualPage magic
|
|
#define NVRAM_MAGIC (0x7710fdb9)
|
|
// Number of emulated cells
|
|
#define NVRAM_LENGTH 3072
|
|
// Log configuration: Address index in bits
|
|
#define NVRAM_ADDR_POS 20
|
|
// Log configuration: Mask for comparsion (4k space)
|
|
#define NVRAM_ADDR_MASK 0xfff00000
|
|
// Log configuration: Bit position of used address bitmap
|
|
#define NVRAM_BITMAP_POS 8
|
|
// Log configuration: used address bitmap calulation
|
|
#define NVRAM_BITMAP_ADDR_SHIFT 8
|
|
// Log configuration: Mask for bitmap extraction
|
|
#define NVRAM_BITMAP_MASK 0x000fff00
|
|
#define ADDR2BIT(index) \
|
|
((1 << (index >> NVRAM_BITMAP_ADDR_SHIFT)) << NVRAM_BITMAP_POS)
|
|
|
|
NVRAMClass NVRAM;
|
|
|
|
uint16_t NVRAMClass::length() const
|
|
{
|
|
return (NVRAM_LENGTH);
|
|
}
|
|
|
|
void NVRAMClass::read_block(uint8_t *dst, uint16_t idx, uint16_t n)
|
|
{
|
|
uint32_t *vpage;
|
|
uint16_t log_start, log_end;
|
|
|
|
// find correct page
|
|
vpage = get_page();
|
|
|
|
// copy 0xff to dst when no page is available
|
|
if (vpage == (uint32_t *)~0) {
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
((uint8_t *)dst)[i] = 0xff;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// calculate actual log position
|
|
log_end = get_log_position(vpage);
|
|
if (log_end == 0) {
|
|
log_start = 1;
|
|
} else {
|
|
log_start = vpage[0] + 1;
|
|
}
|
|
/*
|
|
Serial.print("\r\nread_block idx=");
|
|
Serial.print(idx);
|
|
Serial.print(" n=");
|
|
Serial.print(n);
|
|
Serial.print("("); */
|
|
while (n > 0) {
|
|
// Read cell
|
|
*dst = get_byte_from_page(vpage, log_start, log_end, idx);
|
|
// Serial.print(*dst, HEX);
|
|
// calculate next address
|
|
n--;
|
|
dst++;
|
|
idx++;
|
|
}
|
|
// Serial.println(")");
|
|
}
|
|
|
|
uint8_t NVRAMClass::read(const uint16_t idx)
|
|
{
|
|
uint8_t ret;
|
|
read_block(&ret, idx, 1);
|
|
return ret;
|
|
}
|
|
|
|
bool NVRAMClass::write_block(uint8_t *src, uint16_t idx, uint16_t n)
|
|
{
|
|
uint32_t *vpage;
|
|
uint32_t bitmap;
|
|
uint16_t log_start, log_end;
|
|
|
|
// find correct page
|
|
vpage = get_page();
|
|
|
|
// return on invalid page
|
|
if (vpage == (uint32_t *)~0) {
|
|
return false;
|
|
}
|
|
|
|
// calculate actual log position
|
|
log_start = vpage[0] + 1;
|
|
log_end = get_log_position(vpage);
|
|
if (log_end > log_start) {
|
|
bitmap = vpage[log_end - 1] & NVRAM_BITMAP_MASK;
|
|
} else {
|
|
bitmap = 0;
|
|
}
|
|
|
|
while (n > 0) {
|
|
// Read cell
|
|
uint8_t old_value = get_byte_from_page(vpage, log_start, log_end, idx);
|
|
uint8_t new_value = *src;
|
|
|
|
// Have to write into log?
|
|
if (new_value != old_value) {
|
|
|
|
// need to calculate a new page?
|
|
if (log_end >= VirtualPage.length()) {
|
|
vpage = switch_page(vpage, &log_start, &log_end);
|
|
if (vpage == (uint32_t *)~0) {
|
|
// do nothing if no page is available
|
|
return false;
|
|
}
|
|
bitmap = 0;
|
|
}
|
|
|
|
// Add Entry into log
|
|
Flash.write(&vpage[log_end], (idx << NVRAM_ADDR_POS) | bitmap |
|
|
ADDR2BIT(idx) | (uint32_t)new_value);
|
|
log_end++;
|
|
}
|
|
|
|
// calculate next address
|
|
n--;
|
|
src++;
|
|
idx++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NVRAMClass::write(uint16_t idx, uint8_t value)
|
|
{
|
|
return (write_block(&value, idx, 1));
|
|
}
|
|
|
|
int NVRAMClass::write_prepare(uint16_t number)
|
|
{
|
|
// find correct page
|
|
uint32_t *vpage = get_page();
|
|
// Want to write to much or into an invalid page?
|
|
if ((vpage == (uint32_t *)~0) || (number > length())) {
|
|
return -1;
|
|
}
|
|
|
|
// calculate actual log position
|
|
uint16_t log_end = get_log_position(vpage);
|
|
|
|
// Calculate number of free bytes in log
|
|
int free_bytes = ((VirtualPage.length() - 1) - log_end);
|
|
|
|
// switch page when
|
|
if (free_bytes < number) {
|
|
uint16_t log_start = vpage[0] + 1;
|
|
vpage = switch_page(vpage, &log_start, &log_end);
|
|
if (vpage == (uint32_t *)~0) {
|
|
// do nothing if no page is available
|
|
return -1;
|
|
}
|
|
log_end = get_log_position(vpage);
|
|
free_bytes = ((VirtualPage.length() - 1) - log_end);
|
|
}
|
|
return free_bytes;
|
|
}
|
|
|
|
void NVRAMClass::clean_up(uint16_t write_preserve)
|
|
{
|
|
VirtualPage.clean_up();
|
|
if (write_preserve > 0) {
|
|
write_prepare(write_preserve);
|
|
}
|
|
}
|
|
|
|
uint32_t *NVRAMClass::switch_page(uint32_t *old_vpage, uint16_t *log_start,
|
|
uint16_t *log_end)
|
|
{
|
|
// Mark old page as in release
|
|
VirtualPage.release_prepare(old_vpage);
|
|
|
|
// Get a blank page
|
|
uint32_t *new_vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length());
|
|
if (new_vpage == (uint32_t *)~0) {
|
|
// failed
|
|
return new_vpage;
|
|
}
|
|
|
|
// Store four bytes for map creation
|
|
uint32_t value;
|
|
|
|
// Length of new map
|
|
uint16_t map_length = 0;
|
|
|
|
// Build map
|
|
#ifdef FLASH_SUPPORTS_RANDOM_WRITE
|
|
// Copy current values
|
|
for (uint16_t i = 0; i < (NVRAM_LENGTH >> 2); i++) {
|
|
read_block((uint8_t *)&value, i << 2, 4);
|
|
if (value != (uint32_t)~0) {
|
|
// Value found
|
|
map_length = i + 1;
|
|
Flash.write(&new_vpage[i + 1], value);
|
|
}
|
|
}
|
|
// Store map length
|
|
Flash.write(new_vpage, map_length);
|
|
#else
|
|
// find map length
|
|
for (uint16_t i = (NVRAM_LENGTH >> 2); i > 0; i--) {
|
|
read_block((uint8_t *)&value, i << 2, 4);
|
|
if (value != (uint32_t)~0) {
|
|
// Value found
|
|
map_length = i;
|
|
break;
|
|
}
|
|
}
|
|
map_length++;
|
|
|
|
// Store map length
|
|
Flash.write(new_vpage, map_length);
|
|
|
|
// Copy current values
|
|
for (uint16_t i = 0; i <= map_length; i++) {
|
|
read_block((uint8_t *)&value, i << 2, 4);
|
|
if (value != (uint32_t)~0) {
|
|
// Value found
|
|
map_length = i;
|
|
Flash.write(&new_vpage[i + 1], value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Release old page
|
|
VirtualPage.release(old_vpage);
|
|
|
|
// Set log position
|
|
*log_start = map_length + 1;
|
|
*log_end = *log_start;
|
|
|
|
return new_vpage;
|
|
}
|
|
|
|
uint32_t *NVRAMClass::get_page()
|
|
{
|
|
uint32_t *vpage = VirtualPage.get(NVRAM_MAGIC);
|
|
// Invalid page?
|
|
if (vpage == (uint32_t *)~0) {
|
|
// Allocate a new page
|
|
vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length());
|
|
// Set map length to 0
|
|
Flash.write(&vpage[0], 0x0);
|
|
}
|
|
return vpage;
|
|
}
|
|
|
|
uint16_t NVRAMClass::get_log_position(uint32_t *vpage)
|
|
{
|
|
uint16_t position_min = vpage[0] + 1;
|
|
uint16_t position_max = VirtualPage.length();
|
|
|
|
// Return if page is not filled
|
|
if ((vpage[0] == (uint32_t)~0) || (position_min >= position_max)) {
|
|
return 0;
|
|
}
|
|
|
|
// loop until postition_min != position_max-1
|
|
while (position_min != position_max - 1) {
|
|
// Calculate middle between min and max
|
|
uint16_t mid = position_min + ((position_max - position_min) >> 1);
|
|
// Set max or min to current position
|
|
if (vpage[mid] == (uint32_t)~0) {
|
|
position_max = mid;
|
|
} else {
|
|
position_min = mid;
|
|
}
|
|
}
|
|
|
|
return position_max;
|
|
}
|
|
|
|
uint8_t NVRAMClass::get_byte_from_page(uint32_t *vpage, uint16_t log_start,
|
|
uint16_t log_end, uint16_t idx)
|
|
{
|
|
// mask matching a bit signaling wich address range is in log
|
|
uint32_t address_mask = ADDR2BIT(idx);
|
|
// mask matching the index address
|
|
uint32_t address_match = idx << NVRAM_ADDR_POS;
|
|
|
|
// Check the log backwards
|
|
while (log_end > log_start) {
|
|
log_end--;
|
|
uint32_t value = vpage[log_end];
|
|
// end here if address map bit is not set
|
|
if ((value & address_mask) == 0) {
|
|
break;
|
|
}
|
|
// check address match -> update found -> return
|
|
if ((value & NVRAM_ADDR_MASK) == address_match) {
|
|
return (uint8_t)value;
|
|
}
|
|
}
|
|
|
|
// Calculate address in the eeprom map at the beginning of a vpage
|
|
uint16_t map_address = (idx >> 2);
|
|
map_address++; // jump over log offset field
|
|
|
|
// look at map if calculated addess before log start position
|
|
if (map_address < log_start) {
|
|
switch (idx % 4) {
|
|
case 3:
|
|
return (uint8_t)(vpage[map_address] >> 24);
|
|
break;
|
|
case 2:
|
|
return (uint8_t)(vpage[map_address] >> 16);
|
|
break;
|
|
case 1:
|
|
return (uint8_t)(vpage[map_address] >> 8);
|
|
break;
|
|
default:
|
|
return (uint8_t)(vpage[map_address]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// empty cell
|
|
return 0xff;
|
|
}
|