NVMEM Framework in Linux
Continue this free Linux kernel development course with a deep dive into non-volatile memory access
Every SoC ships with a handful of bytes that never change once written: a MAC address burned into an EEPROM, a calibration value stuck in an eFuse, a board revision ID sitting in a tiny I2C memory chip. If you’ve been following this free Linux kernel development course, you already know the kernel likes to abstract recurring hardware patterns behind a single framework instead of letting every driver reinvent the wheel. NVMEM — short for Non-Volatile MEMory — is exactly that abstraction for read-mostly storage devices, and it’s the subject of this lecture.
What You Will Learn
- Why the kernel introduced a dedicated NVMEM framework instead of letting each EEPROM/eFuse driver do its own thing
- The producer/consumer design NVMEM shares with other kernel subsystems you’ve seen in this free Linux kernel development course
- What a “cell” is and why it’s the real unit of work in NVMEM
- Every field of
struct nvmem_deviceandstruct nvmem_config, explained in plain language - How to write a minimal, original NVMEM provider skeleton against the current kernel API
- Mistakes beginners make the first time they register an NVMEM device
Prerequisites
- Comfortable reading and writing C
- Basic platform driver / probe-remove experience (covered earlier in this free Linux device drivers course)
- A Linux kernel 6.x source tree for cross-referencing (
drivers/nvmem/,include/linux/nvmem-provider.h,include/linux/nvmem-consumer.h) - Device Tree basics are helpful but not required for this lecture
Why This Free Linux Kernel Development Course Covers NVMEM
Before NVMEM existed, drivers for EEPROMs, eFuses, and similar small storage chips lived scattered across drivers/misc/. Each one exposed its own sysfs attributes, its own ioctl-like conventions, and its own way of letting a consumer driver ask “give me my MAC address.” That duplication meant every new chip needed a fresh implementation of essentially the same read/write plumbing, and every consumer driver needed chip-specific glue to talk to whichever provider happened to be on that board.
NVMEM solves this the same way the kernel solves most duplication problems: pull the common behavior into a shared framework, and let individual chip drivers register into it instead of reimplementing it. On top of that, NVMEM adds first-class Device Tree support so a consumer node can simply point at a “cell” — a named, offset-based slice of the storage — without caring which physical chip backs it.
What Is the NVMEM Framework?
At its core, NVMEM is deliberately small. It doesn’t try to be a filesystem or a block layer — it’s a thin, generic layer that lets a provider driver (the code that actually knows how to talk to the EEPROM, eFuse, or OTP memory over I2C, SPI, or a memory-mapped register) expose raw storage, while any number of consumer drivers pull named regions out of it without knowing the underlying transport at all.
The unit consumers actually ask for is called a cell: a named byte range inside the NVMEM device, such as “mac-address” at offset 0x10 for 6 bytes, or “board-id” at offset 0x00 for 2 bytes. Cells can be defined by the provider driver in C, or declared entirely in Device Tree, which is the more common approach on modern boards.
The Producer/Consumer Design of NVMEM
If you’ve worked through the Common Clock Framework earlier in this course, this pattern will feel familiar: a single provider driver registers a device that exposes capability, and any number of unrelated consumer drivers request pieces of that capability by name. NVMEM applies the exact same idea to storage instead of clocks.
- A provider driver includes
<linux/nvmem-provider.h>and registers itself with the framework, describing how to read (and optionally write) raw bytes. - A consumer driver includes
<linux/nvmem-consumer.h>and looks up a named cell, with zero knowledge of whether that cell lives on an I2C EEPROM, a SPI flash region, or an SoC eFuse bank.
This separation is what makes NVMEM valuable in practice: swap the physical storage chip on a board redesign, update the Device Tree, and every consumer driver keeps working unmodified.
Core NVMEM Data Structures
NVMEM deliberately keeps its data structures minimal. Two structures matter most when you’re writing a provider driver, and understanding the difference between them clears up almost all of the initial confusion.
struct nvmem_config — What You Fill In
struct nvmem_config is the configuration you, the provider driver author, populate and hand to the framework at registration time. Think of it as a request form.
| Field | Purpose |
|---|---|
| dev | Parent device (usually your platform/I2C/SPI device) |
| name / id | Optional device name; final name becomes <name><id>, or nvmem<id> if omitted |
| owner | Owning module, almost always THIS_MODULE |
| cells / ncells | Optional array of predefined cells and its length |
| read_only | Marks the whole device non-writable |
| root_only | Restricts userspace access to root |
| reg_read / reg_write | Your callbacks that actually move bytes to/from hardware |
| size / word_size / stride | Total size, minimum access granularity, and minimum access stride |
| priv | Opaque context pointer passed back into your read/write callbacks |
struct nvmem_device — What the Framework Gives Back
struct nvmem_device is created and owned by the framework itself once you register your config. It’s effectively a runtime copy of the fields you supplied, plus bookkeeping the framework needs internally, such as a users reference count. As a provider author you rarely touch this structure directly — you interact with the handle the registration function returns.
Step 1 — Hardware
Step 2 — Provider Driver
Step 3 — Named Cells Exposed
Step 4 — Consumers
Building a Minimal NVMEM Provider Driver
Let’s put the structures to work with a small, original example — a fake NVMEM device backed by a static in-memory buffer, so you can test the registration path with no real hardware attached. This is deliberately simplified for this stage of the free Linux kernel development course; a follow-up lecture will build a complete provider against a real I2C EEPROM.
// ep_nvmem_fake.c — minimal NVMEM provider skeleton
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/nvmem-provider.h>
#define EP_NVMEM_SIZE 32
struct ep_nvmem_priv {
u8 storage[EP_NVMEM_SIZE];
};
static int ep_nvmem_read(void *priv, unsigned int offset,
void *val, size_t bytes)
{
struct ep_nvmem_priv *ep = priv;
if (offset + bytes > EP_NVMEM_SIZE)
return -EINVAL;
memcpy(val, ep->storage + offset, bytes);
return 0;
}
static int ep_nvmem_write(void *priv, unsigned int offset,
void *val, size_t bytes)
{
struct ep_nvmem_priv *ep = priv;
if (offset + bytes > EP_NVMEM_SIZE)
return -EINVAL;
memcpy(ep->storage + offset, val, bytes);
return 0;
}
static int ep_nvmem_probe(struct platform_device *pdev)
{
struct ep_nvmem_priv *ep;
struct nvmem_config cfg = { 0 };
struct nvmem_device *nvmem;
ep = devm_kzalloc(&pdev->dev, sizeof(*ep), GFP_KERNEL);
if (!ep)
return -ENOMEM;
/* Pretend byte 0-5 already holds a MAC address for this demo */
memset(ep->storage, 0xAA, EP_NVMEM_SIZE);
cfg.dev = &pdev->dev;
cfg.name = "ep-fake-nvmem";
cfg.id = -1;
cfg.owner = THIS_MODULE;
cfg.size = EP_NVMEM_SIZE;
cfg.word_size = 1;
cfg.stride = 1;
cfg.reg_read = ep_nvmem_read;
cfg.reg_write = ep_nvmem_write;
cfg.priv = ep;
nvmem = devm_nvmem_register(&pdev->dev, &cfg);
if (IS_ERR(nvmem))
return dev_err_probe(&pdev->dev, PTR_ERR(nvmem),
"failed to register NVMEM device\n");
platform_set_drvdata(pdev, nvmem);
dev_info(&pdev->dev, "ep-fake-nvmem registered, %d bytes\n",
EP_NVMEM_SIZE);
return 0;
}
static struct platform_driver ep_nvmem_driver = {
.probe = ep_nvmem_probe,
.driver = {
.name = "ep-fake-nvmem",
},
};
module_platform_driver(ep_nvmem_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala demo NVMEM provider");
Build and load it against a manually registered platform device (or bind it via Device Tree in a real board):
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_nvmem_fake.ko
Expected dmesg output:
$ sudo dmesg | tail -n 3
[ 812.441207] ep-fake-nvmem ep-fake-nvmem.0: ep-fake-nvmem registered, 32 bytes
You can confirm the device appeared through the generic NVMEM sysfs interface:
$ ls /sys/bus/nvmem/devices/
ep-fake-nvmem0
$ sudo hexdump -C /sys/bus/nvmem/devices/ep-fake-nvmem0/nvmem | head -1
00000000 aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa aa
Common Mistakes When Registering NVMEM Devices
- Forgetting bounds checks in reg_read/reg_write — the framework trusts your callback to validate
offset + bytesagainst your device size; skipping this is a straightforward out-of-bounds write. - Using
nvmem_register()instead ofdevm_nvmem_register()without a matching unregister call in your remove path, leaking the device on module unload. - Setting
word_size/strideto 0 — leave them at 1 unless your hardware genuinely enforces wider access granularity. - Assuming
cellsis required — it’s optional; most modern drivers let Device Tree define cells instead of hardcoding them in C.
Best Practices for NVMEM Provider Drivers
- Prefer the
devm_managed registration variant so cleanup is automatic on probe failure or driver unbind. - Keep
reg_read/reg_writecallbacks fast and non-sleeping-friendly where the underlying bus allows it; if the bus can sleep (I2C/SPI), that’s fine — just don’t take locks that could deadlock against the NVMEM core. - Set
read_onlywhenever the hardware genuinely can’t be written (many eFuse/OTP parts), rather than relying on consumers to behave.
Security and Access Considerations
Some NVMEM-backed data — serial numbers, security keys, calibration trim values — shouldn’t be readable by every userspace process. Use root_only where appropriate, and remember it gates the generic sysfs/userspace path, not in-kernel consumer access, so a compromised but privileged userspace process can still read root-only cells unless additional LSM policy is layered on top.
Summary and Key Takeaways
- NVMEM replaces one-off EEPROM/eFuse drivers with a shared producer/consumer framework.
- A cell is the named unit consumers actually work with, not raw offsets.
struct nvmem_configis what you fill in;struct nvmem_deviceis what the framework hands back.reg_read/reg_writecallbacks are the only hardware-specific code a provider driver really needs to write.
That’s the full picture of the data structures behind NVMEM. In the next lecture of this free Linux kernel development course, we’ll build a complete provider driver against Device-Tree-declared cells, and after that, look at the consumer-side APIs that let any driver in the kernel pull a named cell without knowing what chip it lives on.
Frequently Asked Questions
What replaced the old drivers/misc/ EEPROM drivers?
The NVMEM framework did. Chip-specific drivers still exist, but they register into NVMEM instead of exposing their own bespoke sysfs interface.
Is this free Linux kernel development course suitable for beginners?
Yes, as long as you’re comfortable with basic C and have followed the earlier platform driver lectures in this free Linux device drivers course — NVMEM builds directly on those concepts.
What exactly is a “cell” in NVMEM?
A named, offset-and-length-defined slice of the NVMEM device’s storage, such as a 6-byte MAC address at a fixed offset.
Do I have to define cells in C code?
No. Most modern boards declare cells entirely in Device Tree, which is the approach we’ll cover in the provider driver lecture.
Can an NVMEM device be write-only or read-only?
It can be marked read-only via nvmem_config.read_only. There’s no equivalent write-only flag; omit reg_read if reads genuinely shouldn’t be supported.
What headers does a provider driver need?
<linux/nvmem-provider.h> for provider drivers, and <linux/nvmem-consumer.h> for anything consuming a cell.
Does NVMEM work without Device Tree?
Yes — cells can be defined directly in nvmem_config.cells for board-file or ACPI-based systems, though DT is by far the most common path today.
Keep Going With This Free Linux Kernel Development Course
Next up: a complete NVMEM provider driver against real Device Tree cells, followed by the consumer-side APIs.
Next Lecture Browse Full Course Index