Zephyr NVS History Reads: Do Not Treat an Older Record as a Guaranteed Backup

nvs_read_hist() selects an older stored entry for one NVS ID. It does not reserve that entry against reclaim, identify an application generation, or promise a fixed number of backups. Use it for qualified historical inspection, and make missing or unsuitable history an explicit application outcome.

This article targets Zephyr v4.2.0 and a single NVS region. It provides a history-selection fixture and fallback decision matrix. It does not cover CRC migration, partial-read integrity testing, or multi-record transactions. Any product promise to restore a particular version needs a separate retention design.

1. Distinguish history index from generation

The versioned NVS API defines history counter zero as the latest entry, one as the entry before it, and so on. The counter is relative to the entries available to that lookup. It is not a timestamp, a firmware version, or the generation number contained in your payload.

If a diagnostic screen asks for “the previous calibration,” define what previous means: the previous physical record, the previous distinct accepted calibration, or the last operator-approved version. Those can differ. Store and validate application identity fields when such distinctions matter, rather than deriving meaning from the requested ordinal.

NVS skips a write when the ID and data are unchanged, as documented in the v4.2.0 storage description. Consequently, three successful application attempts need not create three historical records. A retry or a no-change save should not advance a user-visible revision count merely because the API was called again.

History lookup leads to application identity validation and either a permitted restoration or an explicit unavailable state; reclaim can change available history.
A history ordinal selects a stored entry. It does not reserve an application generation or guarantee backup retention.

2. Understand why history can disappear

The v4.2.0 reclaim implementation checks whether a record is still the latest valid entry for its ID before copying it, then erases the reclaimed sector. Superseded values are not retained as a backup set. An older record that is readable now may disappear after enough unrelated writes cause its sector to be reclaimed.

This creates two independent questions: can NVS recover the current live values, and does a particular historical version still exist? A store can answer the first successfully while no longer satisfying the second. Increasing the region size may extend observed history under one workload, but it does not create a version-retention contract.

3. Build a controlled same-ID fixture

Use an authorized disposable region or test image with enough space for the initial sequence without reclaim. Pin sector geometry, driver version, lookup-cache configuration, and payload format. Give each test value a distinguishable application generation and content marker, and keep an independent expected-value log outside the region under test.

Fixture step Probe Evidence to retain
Write A, then B, then C to one ID. Read history indexes 0, 1, and 2 before reclaim. Returned size, status, payload identity, and expected C/B/A ordering.
Repeat the identical C payload. Repeat the same history queries. Whether the no-change operation leaves the logical observation unchanged.
Request beyond the known fixture depth. Exercise absent-history handling. Actual status and whether the application rejects a missing or unexpected generation.
Churn unrelated IDs through sector reclaim. Repeat the original ID’s history queries after each transition. Which generations remain, and when older ones cease to be available.
Remount and repeat. Compare current-value and historical observations. Whether application behavior remains correct without an assumed history depth.
Delete the ID in a separate test branch. Check current and historical access. Deletion policy remains authoritative; old data is not silently resurrected.

The initial ordering is a fixture expectation under controlled conditions, not a guarantee for arbitrary aged media. Treat a nonnegative result with the wrong application identity as a failed selection, even if the bytes are well formed. This also catches edge cases or implementation differences that a return-code-only test would miss.

4. Handle every result deliberately

The v4.2.0 implementation has -ENOENT paths for unavailable entries and selected deletion records. It can also return errors from lower-level reads. The history read path should be reviewed with the exact build used by the product. Do not flatten all negative results into “use factory defaults.”

  • Expected generation returned: validate the complete application representation and check whether restoration is permitted for the current hardware and firmware.
  • No suitable history available: report that the requested historical version is unavailable; keep a valid current value unless an approved recovery policy says otherwise.
  • Unexpected generation or size: reject the selection and preserve the fixture or image for diagnosis.
  • Device or integrity error: follow the storage-failure path rather than silently treating the error as an empty history.
  • Deleted record: respect why it was deleted. A diagnostic capability to inspect older bytes is not authorization to restore them.

A return value larger than the requested buffer length indicates that the entry contains more data, according to the read API contract. Match the returned representation to the schema and allocated buffer before parsing it. This is a selection and bounds check; the article makes no new integrity guarantee.

5. Keep enumeration stable enough to interpret

If another task writes while a diagnostic loop reads indexes zero, one, and two, each query may refer to a different store state. Coordinate the sequence with the application’s storage ownership mechanism, or label the observations as non-snapshot data. Do not add an undocumented internal NVS lock dependency to application code.

Bound the history search and the number of reads. A diagnostic request should not indefinitely scan for a desired generation while foreground work continues to change the region. Preserve the starting state or a suitable test copy when reproducible evidence matters.

Hypothetical product decision

A service tool offers to display earlier sensor calibrations. The product stores an application generation in each calibration and initially finds the previous two versions through history reads. A churn test later removes one older version while the current calibration remains correct. No measured result is asserted here; this is the scenario the fixture is designed to expose.

The team can retain an opportunistic “available history” display with an explicit unavailable state. If the requirement is instead to restore the last approved calibration after any permitted workload, the team must implement and qualify deliberate retention of that approved object. Calling history index one cannot satisfy that requirement by itself.

Release the feature with the fixture, identity checks, bounded search, and missing-history policy. The API is useful precisely when its relative selection behavior is kept separate from the stronger promise of a backup.

Similar Posts