-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathNvTx.cpp
191 lines (178 loc) · 6.58 KB
/
NvTx.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#include "NvTx.h"
#include "crc16.h"
#include <EEPROM.h>
/*
Transactional non-volatile storage uses EEPROM for reliably storing
client's data. It provides the following useful properties:
- client will not read the data it have never written
- client will not read the data corrupted for any reason
- the data update will succeed either entirely or not at all, in the latter
case old data will be read
- the data is bound to the semantic tag (the string) provided by the client so
the data originally bound to the different tag will not be written to avoid
erroneous interpretation of the semantically different data
To provide the above guarantees the data is saved in 2 copies in 2 adjacent
storage 'cells'. Each copy is protected by 2 checksums calculated based on the
semantic tag and the content of the data. The format of the storage is illustrated below
where the E is epoch bit, V is the valid bit, addr is the base address, size is the data size:
|E| tag chksum |V| tag chksum cont'd | data | tag + data chksum || 2'nd cell ..
^ ^ ^ ^ ^
| addr | addr + 1 | addr + 2 | addr + 2 + size | addr + 4 + size
*/
#define EPOCH 1
#define VALID 8
#define CELL_SIZE(val_size) (val_size + 4)
// Write data to the cell given its base address
static inline void nv_tx_write_cell(
uint16_t id,
const void* val,
unsigned size,
unsigned addr,
uint8_t epoch
)
{
// Make header byte with valid bit set and proper epoch bit
// Other header bits are equal to value id bits
id &= ~(uint16_t)EPOCH;
id |= epoch | VALID;
EEPROM.put(addr, id);
// Use header byte as CRC initial value
uint16_t crc = id;
uint8_t const* ptr = (uint8_t const*)val;
for (addr += 2; size; --size, ++addr, ++ptr) {
uint8_t b = *ptr;
EEPROM.update(addr, b);
crc = crc16_up_(crc, b);
}
// Write final checksum as the tail
EEPROM.put(addr, crc);
}
// Validate the particular storage cell.
// Clear VALID header bit if the cell content is considered as invalid.
// Returns either the value of the epoch bit (0 or 1) or -1 if the cell
// is considered as invalid.
static int8_t nv_tx_validate_cell(
uint16_t id, // value id
unsigned size, // data size
unsigned base_addr // base address of the cell
)
{
uint16_t head, tail, crc;
unsigned addr = base_addr;
EEPROM.get(addr, head);
if ((id | VALID | EPOCH) != (head | EPOCH))
goto invalid;
crc = head;
for (addr += 2; size; --size, ++addr) {
uint8_t b = EEPROM.read(addr);
crc = crc16_up_(crc, b);
}
EEPROM.get(addr, tail);
if (tail != crc)
goto invalid;
return head & EPOCH;
invalid:
// Clear valid bit should head or tail checksum mismatch
// So we can skip checksum verification in all subsequent nv_tx_put calls
if (head & VALID)
EEPROM.write(base_addr, 0);
return -1;
}
// Make value id from tag, instance id and value size
static inline uint16_t nv_tx_value_id(
const char* tag, // the value tag is the string name of the value that serves to identify its semantic
unsigned inst_id,// to discriminate instances with the same tag
unsigned size // the value size in bytes
)
{
return crc16_up_str(crc16_up(inst_id, &size, sizeof(size)), tag);
}
// Read data from the cell given its base address
static inline void nv_tx_read_cell(
void* val,
unsigned size,
unsigned addr
)
{
uint8_t* ptr = (uint8_t*)val;
for (addr += 2; size; --size, ++addr, ++ptr)
*ptr = EEPROM.read(addr);
}
// Read the stored value. Returns true if the value was successfully read. This function should always be called
// before nv_tx_put to format storage even in case you are not interested in the current value.
bool nv_tx_get(
const char* tag, // the value tag is the string name of the value that serves to identify its semantic
unsigned inst_id,// to discriminate instances with the same tag
void* val, // pointer to the memory location where the value will be copied should the read succeed
unsigned size, // the value size in bytes
unsigned addr // the EEPROM base address where the value is stored
)
{
uint16_t const id = nv_tx_value_id(tag, inst_id, size);
unsigned const addr2 = addr + CELL_SIZE(size);
int8_t const valid[2] = {nv_tx_validate_cell(id, size, addr), nv_tx_validate_cell(id, size, addr2)};
if (valid[0] >= 0 && valid[1] >= 0) {
// Both cells have valid data so choose latest based on the epoch bit
nv_tx_read_cell(val, size, valid[0] != valid[1] ? addr : addr2);
return true;
}
// Read value from the valid cell
if (valid[0] >= 0) {
nv_tx_read_cell(val, size, addr);
return true;
}
if (valid[1] >= 0) {
nv_tx_read_cell(val, size, addr2);
return true;
}
return false;
}
// Write new value. The caller is expected to call nv_tx_get first.
void nv_tx_put(
const char* tag, // the value tag is the string name of the value that serves to identify its semantic
unsigned inst_id,// to discriminate instances with the same tag
void const* val, // pointer to the memory location from where the value will be copied to the EEPROM
unsigned size, // the value size in bytes
unsigned addr // the EEPROM base address where the value will be stored
)
{
uint16_t const id = nv_tx_value_id(tag, inst_id, size);
unsigned const addr2 = addr + CELL_SIZE(size);
// Read header bytes from the cells
uint8_t const hdr[2] = {EEPROM.read(addr), EEPROM.read(addr2)};
// Write to the first not valid cell using epoch calculated based on the other cell's epoch
if (!(hdr[0] & VALID))
nv_tx_write_cell(id, val, size, addr, ~hdr[1] & EPOCH);
else if (!(hdr[1] & VALID))
nv_tx_write_cell(id, val, size, addr2, hdr[0] & EPOCH);
// If both cells have valid data choose the next one based on the epoch bits
else if ((hdr[0] & EPOCH) == (hdr[1] & EPOCH))
nv_tx_write_cell(id, val, size, addr, ~hdr[1] & EPOCH);
else
nv_tx_write_cell(id, val, size, addr2, hdr[0] & EPOCH);
}
// Erase stored value so that subsequent nv_tx_get will return false.
void nv_tx_erase(
unsigned size, // the value size in bytes
unsigned addr // the EEPROM base address where the value will be stored
)
{
unsigned const addr2 = addr + CELL_SIZE(size);
// Read header bytes from the cells
uint8_t const hdr[2] = {EEPROM.read(addr), EEPROM.read(addr2)};
if (!(hdr[0] & VALID) && !(hdr[1] & VALID))
return;
// Write 0 byte to the header to invalidate it
if (!(hdr[0] & VALID))
EEPROM.write(addr2, 0);
else if (!(hdr[1] & VALID))
EEPROM.write(addr, 0);
// If both cells have valid data erase older value first
else if ((hdr[0] & EPOCH) == (hdr[1] & EPOCH)) {
EEPROM.write(addr, 0);
EEPROM.write(addr2, 0);
} else {
EEPROM.write(addr2, 0);
EEPROM.write(addr, 0);
}
}