The pvalue subsystem provides FLASH based persistency for system values that require it, for example values vp and forth-wordlist.
Persistency is achieved by writing a rolling log of update records into flash backed PVFLASH memory region. When amforth restarts the log is replayed to initialize the pvalue RAM slots to the latest recorded state (pv.init).
Main goals of the implementation were:
- simplicity - over optimal but more complex solution
- robustness - ability to recover from unexpected resets without losing persisted state
To be able to compact the log when PVFLASH fills up, the region is divided into two equal size arenas, only one of which is active at a time (pvarena). When the arena fills up, compact and swap operation is triggered automatically on next record write attempt (pvarena.swap). If the swap doesn’t complete for whatever reason, it will be attempted again, until it finishes successfully. Swap can also be invoked explicitly at any time.
The log entries (pvalue records) consists of 2 words: 1) ID: identifies which pvalue the record belongs to; the pvalue RAM address is used as the ID 2) VALUE: the new value of the pvalue
First record of the arena is used to capture arena state. The two words have following meaning: 1) COUNTER: starts at 0 and is incremented for each new arena activation after the swap 2) DIRTY: pvflash.erased for blank dormant arena, ~pvflash.erased for arena that has been written into
COUNTER has its MSB always set to make it distinct from usual RAM addresses. It counts up to pvflash.erased (usually -1) so there needs to be enough room for all the swaps (normally shouldn’t be a problem, given the usual flash memory lifetime). The arena record is the last thing written into the new arena during a swap. This allows detecting unfinished swaps, i.e. a swap is not successful until the COUNTER is written. The only other time when arena record is written is during the very first initialization (first-boot).
Sizing the arenas and therefore PVFLASH correctly is important. There needs to be enough room to accommodate all pvalues in the system and have enough room for the expected volume of pvalue updates to avoid frequent compaction and swapping. The arena should be at least half empty (and probably more) after compaction. Arenas also must be erasable independently and therefore must be a multiple of flash erase page size.
Note that pvalue updates should be relatively infrequent to avoid filling up the PVFLASH too quickly. For example dp is not a good candidate because it is updated on every comma and ccomma during compilation. It is better to persist a pointer that moves only once per word, like the forth-wordlist instead.