Storing Vital Product Data with TLV-C
Share
Each product we make has embedded "vital product data" - information which the firmware uses to introspect the multiple hardware revisions or variants it may be running on with a single binary.
In this article, we share how vital product data is stored using TLV-C, as well as the integrity and flexibility that the format provides.
Are you working with CAN devices and need to simplify integration with your compute? Have a look at our range of CAN interface cards. Need something tailored to your needs? Contact us to discuss how we can apply our engineering talent to your problem space.
TLV-C
Type-Length-Value encoding is nothing new; however, the checksum approach that the Oxide Computer Company has taken with their TLV-C crate is unique.
At the start of each chunk, there is a header containing the tag, length, and header checksum. The variable-length value in the chunk also has its own checksum. These chunks are serialized into a stream of chunks, suitable for storing in memory and transmission over any serial interface.
A full description of the encoding can be found in the TLV-C repository.
Gracefully Degraded Data
If the header checksum of the chunk is valid, the length of the value within the chunk is ensured. Even if the value data has become corrupted, it can be safely skipped because the start of the next chunk is known.
Of course, if the header checksum fails, the start of the next chunk cannot be known.
Structured Chunks
Chunks can store TLV-C data recursively. a.k.a. what is required to store structured data.
Like with corrupt chunks, chunks containing large amounts of structured data not relevant to the current operation can be passed over without processing the full tree. This alleviates most concerns about ordering of chunks, or over-normalization causing performance issues.
Soft Superseding
When dealing with OTP memory, there is a certain "burning of the boats" that takes place. This may mean discarding a finished product because the wrong sequence of bytes was written to a non-erasable section of memory.
With TLV-C it is valid to have chunks with duplicate tags and so the firmware can use the last read chunk. Bad OTP can then be superseded with new chunks that have the correct updated values.
The tag itself can also be used to store a simple versioning of the data. 'CFG2' may supersede 'CFG1' while the firmware maintains the ability to handle both tags, supporting older devices.
Unified Host and Target Software
Finally, one of the core reasons for using the Rust programming language is the ability to write these kinds of libraries targeting both target and host environments. The TLV-C implementation provided by Oxide uses a feature flag to provide a subset of functionality for embedded systems (called "no_std" in Rust).
With this, our vital product data deployment workflow is straightforward. First, we pack data from a RON file into TLV-C format. Then, we embed these bytes into a binary which is flashed to the target device. On its first boot, the firmware automatically handles programming this TLV-C data into OTP memory, completing the process.
Really, that's it. We have key-value data, tree-like, strong integrity, good space efficiency, that can be understood by humans, machine hosts and targets alike without needing to invent it our self.