Skip to main content

TECH VEDA

Linux kernel & Device drivers starts on 24th Oct 2026 enrollingCorporate on-site training - Submit proposal Pick your modulesSharpen your kernel skills: deep dives, drivers, Yocto, CVEs, careers โ€” updated daily. Read the blog โ†’Embedded Linux fast track starts 30th sept 2026 enrollingEmbedded Linux Mastery track starts 30th sept 2026 enrollingLinux systems engineering starts 30th sept 2026 enrolling
Deep Dives

Linux nvmem Cells: How Per-Unit Data Reaches Your Driver

How Linux nvmem cells carry a MAC address or calibration data out of an EEPROM or fuse array and into a driver before the device registers.

Linux nvmem Cells: How Per-Unit Data Reaches Your Driver

Linux already has a standard path for moving per-unit data out of an EEPROM or an on-chip fuse array into a driver before the device registers, and most embedded teams reimplement it by hand instead. The mechanism is the nvmem subsystem, and its unit of access is the nvmem cell: a named window into a flat byte array, wired to the driver that needs it in the device tree. Using nvmem cells removes the boot-time fix-up script and the duplicate MAC address that comes with one device tree shared across a production run. Getting it wrong is usually silent, because several network drivers fall back to a random address rather than report the failure.

If you ship a board in volume, some of what the kernel needs differs on every unit. The MAC address is the obvious case; calibration constants, a serial number, a speed grade and a factory-programmed key are the same kind of thing, written once per unit at manufacture into an EEPROM or the SoC’s own fuse array. The kernel’s unit of access for this is the nvmem cell. This article traces what an nvmem cell is, the code path a driver takes to read one, what changes across longterm kernels, and where the mechanism breaks.

The problem: one image, many boards, one MAC address

Start with what most projects do. The Ethernet controller takes its address from the device tree, so the address goes in the board file:

&fec1 {
	phy-mode = "rmii";
	local-mac-address = [00 04 9f 05 4b 22];
	status = "okay";
};

That works on the bring-up board and stops working at the second unit, because every board flashed from that image claims the same address and the device tree is part of the image. What you see depends on the network, which is part of why it is hard to diagnose. On a managed switch the forwarding table keeps moving the address between two ports, so traffic for either unit reaches whichever spoke last; a DHCP server keyed on the MAC hands both units the same lease. None of that is a clean error. It is a device that is reachable most of the time.

The usual answer is to program the real address into an EEPROM at manufacture and read it back at boot from a script. Reading it is simple:

raghu@techveda.org:~$ hexdump -C /sys/bus/i2c/devices/1-0050/eeprom | head -2
00000000  53 4c 32 38 01 00 00 00  00 04 9f 05 4b 22 00 00  |SL28........K"..|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

Why the boot-time fix-up does not hold

Applying the address is where this approach fails:

raghu@techveda.org:~$ ip link set dev eth0 address 00:04:9f:05:4b:22
RTNETLINK answers: Device or resource busy

That is neither a permissions problem nor a driver bug. An Ethernet address change goes through eth_mac_addr(), which calls eth_prepare_mac_addr_change() in net/ethernet/eth.c:

int eth_prepare_mac_addr_change(struct net_device *dev, void *p)
{
	struct sockaddr *addr = p;

	if (!(dev->priv_flags & IFF_LIVE_ADDR_CHANGE) && netif_running(dev))
		return -EBUSY;
	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;
	return 0;
}

Unless the driver has declared that it can change its address while running, a running interface refuses the change, so the script must bring the interface down, change the address and bring it back up. That turns a scripting problem into a sequencing problem. The interface already exists by the time any script runs: the driver called register_netdev() during probe with whatever address it could find, so anything watching for a new interface has already seen it and may have brought it up, taken a DHCP lease, or recorded the address. Taking it down and back up invalidates all of that, and each consumer recovers differently. Early in a project there is one such consumer and the script appears to work; later there are five, and the failure shows up as a unit that occasionally comes up with the wrong lease.

Harder to spot is the case where the driver finds no address at all, because it does not fail. In enetc_setup_mac_address() the ENETC driver falls back to the address the bootloader left in the hardware, and if that is also unusable it calls eth_random_addr() and logs a line built from the format string "no MAC address specified for SI%d, using %pM". The unit works, its address changes on every boot, its DHCP lease changes with it, and nothing in the log looks like an error.

What the nvmem subsystem actually is

The framework that removes all of this is small, and understanding it is mostly understanding one design decision. On the provider side, a driver for an EEPROM or a fuse block fills in a struct nvmem_config and calls devm_nvmem_register(). The fields that define the device are these:

	nvmem_reg_read_t	reg_read;
	nvmem_reg_write_t	reg_write;
	int	size;
	int	word_size;
	int	stride;

That is the model: size bytes, readable and possibly writable in word_size units at stride alignment. There is a type field, one of NVMEM_TYPE_UNKNOWN, NVMEM_TYPE_EEPROM, NVMEM_TYPE_OTP, NVMEM_TYPE_BATTERY_BACKED or NVMEM_TYPE_FRAM, plus read_only and root_only. There is nothing about what the bytes mean.

That omission is the design decision. The meaning of the bytes belongs to the board, not the silicon: the same i.MX fuse block holds a MAC address at one offset on one product and a calibration blob at that offset on another. If the provider knew the layout, every new board would need a patch in drivers/nvmem/. Leaving the provider as an unstructured byte array, and describing the layout where the board already describes itself, gives one driver per silicon block and no driver change per board. That is why the subsystem contains less code than you might expect.

The layout lives in the device tree, and what it describes is the nvmem cell:

struct nvmem_cell_info {
	const char		*name;
	unsigned int		offset;
	size_t			raw_len;
	unsigned int		bytes;
	unsigned int		bit_offset;
	unsigned int		nbits;
	struct device_node	*np;
	nvmem_cell_post_process_t read_post_process;
	void			*priv;
};

A name, a window given as offset and length, an optional bit range inside it, and an optional callback. Everything the framework does with nvmem cells is built from those fields.

How a driver finds its nvmem cells

The provider declares its cells as child nodes. This is the i.MX OCOTP binding example, and the shape is the same for every provider:

ocotp: efuse@21bc000 {
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "fsl,imx6sx-ocotp", "syscon";
	reg = <0x021bc000 0x4000>;
	clocks = <&clks IMX6SX_CLK_OCOTP>;

	cpu_speed_grade: speed-grade@10 {
		reg = <0x10 4>;
	};

	tempmon_calib: calib@38 {
		reg = <0x38 4>;
	};

	tempmon_temp_grade: temp-grade@20 {
		reg = <0x20 4>;
	};
};

Because the provider sets #address-cells and #size-cells to 1, each child’s reg is an offset and a length inside the byte array, and a labelled child node is a cell. The consumer then names the cells it wants. The fragment below is an illustration rather than a quote from the tree, because the names on the consumer side are whatever the consuming driver asks for:

&tempmon {
	nvmem-cells = <&tempmon_calib>, <&tempmon_temp_grade>;
	nvmem-cell-names = "calib", "temp_grade";
};

Two parallel lists: phandles in nvmem-cells, names in nvmem-cell-names. The names belong to the consumer, chosen by the driver that reads them, not by the provider. A driver calls devm_nvmem_cell_get(dev, "calib"), which reaches of_nvmem_cell_get() in drivers/nvmem/core.c, and three things happen in order:

  1. of_property_match_string() finds "calib" in the consumer’s nvmem-cell-names and yields an index.
  2. of_parse_phandle_with_optional_args() takes that index into nvmem-cells and yields the provider’s cell node. If either property is missing or the index is out of range, the result is -ENOENT.
  3. The provider node is resolved to a registered nvmem device by __nvmem_device_get(). If nothing registered matches that node:
    	if (!nvmem)
    		return ERR_PTR(-EPROBE_DEFER);

That line is the whole ordering mechanism, and what it is not is worth noticing: no notifier, no completion, no lock held across probe, no ordering declared in the device tree. A consumer that asks for an nvmem cell before the provider has registered is told to come back later, and the ordinary deferred-probe machinery retries it. It is the same mechanism a driver meets when asking for a clock or a regulator that is not there yet, which is why no new machinery was needed.

One extra piece of syntax turns up in real board files, where a cell yields several values and the phandle carries an argument. From the Kontron SMARC-sAL28 board file, where one base MAC address in OTP supplies several ports:

&enetc_port0 {
	nvmem-cells = <&base_mac_address 0>;
	nvmem-cell-names = "mac-address";
};

The 0 indexes into the cell, enabled on the provider side by #nvmem-cell-cells = <1>. Port 1 uses <&base_mac_address 1>, and the provider decides what stepping by one means.

The read path, byte by byte

Reading is nvmem_cell_read(), and underneath it __nvmem_cell_read() does three things in a fixed order. First it reads raw bytes: nvmem_reg_read() calls the provider’s reg_read at the cell’s offset, and nothing is interpreted.

Second, if the cell has a bit range, nvmem_shift_read_buffer_in_place() shifts the buffer right by bit_offset across byte boundaries and masks off the bits above nbits, ending with:

	if (cell->nbits % BITS_PER_BYTE)
		*p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);

The consequence matters more than the code. A cell declared bits = <2 4> hands the consumer a right-aligned, masked 4-bit value, not the containing byte with the other bits in place. A driver written before the cell gained a bit range, which shifts the value itself, shifts it twice.

Third, if the cell has a read_post_process callback, it runs:

	rc = cell->read_post_process(cell->priv, id, index, cell->offset, buf, cell->raw_len);

The hook exists because some silicon stores a value in a form the rest of the kernel does not expect, which is a fact about the silicon rather than the board. The clearest example in the tree is drivers/nvmem/imx-ocotp.c:

static int imx_ocotp_cell_pp(void *context, const char *id, int index,
			     unsigned int offset, void *data, size_t bytes)
{
	u8 *buf = data;
	int i;

	/* Deal with some post processing of nvmem cell data */
	if (id && !strcmp(id, "mac-address"))
		for (i = 0; i < bytes / 2; i++)
			swap(buf[i], buf[bytes - i - 1]);

	return 0;
}

The i.MX fuse words hold the MAC address in the opposite byte order from what the network stack wants, so the driver reverses it, installing the callback on every cell parsed from the device tree through a helper assigned to nvmem_config.fixup_dt_cell_info:

static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
					 struct nvmem_cell_info *cell)
{
	cell->read_post_process = imx_ocotp_cell_pp;
}

Note what the condition tests. Not the offset: the name the consumer used. The reason is worth stating, though this reading is my interpretation rather than something the commit spells out. The offset of the MAC fuses differs from product to product, so an offset test would need updating per board and would put board knowledge back into a silicon driver. The name does not differ, because "mac-address" is what the network stack itself asks for. Keying on the consumer’s name lets one rule cover every i.MX board, and it is why that callback signature carries an id parameter at all.

A worked example: the MAC address, before and after

Before, the address was a literal in the board file and a script patched it at boot. After, it is a cell. What follows is the SMARC-sAL28 board file, which does this in-tree; it is a different SoC from the &fec1 example above, so read it as the pattern rather than as the same board converted, because the pattern is identical whichever provider holds the data. The provider half is the interesting one:

otp-1 {
	compatible = "user-otp";

	nvmem-layout {
		compatible = "kontron,sl28-vpd";

		serial_number: serial-number {
		};

		base_mac_address: base-mac-address {
			#nvmem-cell-cells = <1>;
		};
	};
};

Those cells have no reg, and that is not an omission. This provider uses an nvmem layout: a small driver, matched by the compatible on the nvmem-layout node, that reads the OTP contents at runtime and creates the cells from what it finds. The board file says which layout applies and names the cells it references; the offsets come from the data. For a vendor format that already has a header and a checksum, that is better than repeating offsets in every board file.

The consumer half is the four lines shown earlier on &enetc_port0, and nothing else is needed, because the network stack already looks for that cell. of_get_mac_address() in net/core/of_net.c tries four sources in order: the mac-address property, then local-mac-address, then address, then of_get_mac_address_nvmem(), which asks for a cell named exactly "mac-address". This is not new; the 5.10 longterm kernel has it in drivers/of/of_net.c as of_get_mac_addr_nvmem().

Two properties of the result are what make this worth doing. The address is in place before the interface exists, because enetc_setup_mac_address() runs during probe and the address it produces is the one register_netdev() publishes; no interface is ever created with the wrong address, so nothing downstream sees a change and no script has to handle -EBUSY. And the ordering resolves itself, provided the driver allows it:

	if (err == -EPROBE_DEFER)
		return err;

So if the SPI flash holding the OTP has not registered its nvmem device yet, the port probe is deferred and retried. The check afterwards is ordinary, and the output looks the same as it did with the script:

raghu@techveda.org:~$ ip -brief link show eth0
eth0             UP             00:04:9f:05:4b:22 <BROADCAST,MULTICAST,UP,LOWER_UP>

The difference is that this is the address the interface was created with rather than one applied afterwards, and it came from the unit’s own OTP rather than from the image. One device tree, any number of units, no fix-up script.

What your kernel version gives you

The parts you are most likely to want are not in every longterm kernel. The table was built by reading the headers and drivers/nvmem/core.c at each base release tag, so it says what the series started with; a vendor tree or a later point release may carry backports that move a row.

SeriesCells in DT, consumer APIPost-process hooknvmem-layoutPer-cell sysfs files
5.10 LTSyesnonenono
5.15 LTSyesnonenono
6.1 LTSyesone cell_post_process per providernono
6.6 LTSyesper-cell read_post_process, plus raw_lenyes, registered by the layout driverno
6.12 LTSyesper-cell, plus fixup_dt_cell_infoyes, layout is a device in its own rightyes
6.18 LTSyesas 6.12yesyes

Two notes on that table. For 6.18 I read drivers/nvmem/core.c and confirmed the per-cell sysfs code and the deferred-probe return, but did not re-read its provider header, so treat that row’s post-process column as inherited from 6.12 rather than separately checked. And the 6.1 row is a real capability difference: one cell_post_process for the whole provider has to distinguish cells itself, which is why the hook moved into the cell in 6.6. In practice: on 5.10 or 5.15 nvmem cells and the MAC-address case work, but per-cell transformation has to happen in the consumer; on 6.6 the provider interface is modern but userspace still sees only the whole device; per-cell sysfs files start at 6.12.

Where nvmem cells break, and what they cost

The mechanism is small, so most of the problems are in how consumers use it.

A consumer that swallows -EPROBE_DEFER fails silently. Check this first. ENETC propagates the error; other drivers treat any lookup failure as “no address configured” and fall back to a hardware register or a random address. The board boots, the interface works, the address is wrong. Read the consumer driver before trusting a cell.

nvmem does not know the byte order. nvmem_cell_read_u32() goes through a helper that requires the cell size to match sizeof(u32) exactly, so a three-byte cell fails rather than being zero-extended. For narrower values there is nvmem_cell_read_variable_le_u32() and its 64-bit counterpart, which name their endianness in the function. Outside those helpers you get bytes, and the interpretation is yours.

Bit cells are already shifted, as above, and that is the most likely source of a wrong value when converting an existing driver to nvmem cells.

Writing is not symmetrical with reading. nvmem_cell_write() needs the provider to have a reg_write and to not be marked read_only; sysfs follows the same rule through nvmem_bin_attr_get_umode(), which strips the write bits when the device is read-only or has no write callback. On OTP and eFuse the deeper limit is physical: a programmed bit cannot be cleared, so a wrong value written to a fuse is permanent for that unit.

Per-cell sysfs files are read-only. They are created at mode 0444 masked by the device’s own mode, so even where the raw device file is writable the per-cell files are not. Writing still means the raw file and your own offset arithmetic.

The raw sysfs file is the whole device. Before 6.12 that is the only userspace view, so a script wanting one value must know its offset. That duplicates the layout into a second place, and the two copies drift. With userspace consumers and a choice of kernel version, this argues for 6.12 or later.

A layout built as a module adds a dependency you may not have planned. From 6.6 the cell lookup takes a reference on the layout module and can return -EPROBE_DEFER because the module is not loaded yet. The implication is that a layout module on the root filesystem, with a consumer that probes before that filesystem is mounted, leaves the consumer deferred. I have not reproduced this on hardware, so take it as what the code path implies rather than a measured result; the safe course is to build the layout in or put it in the initramfs.

Finally, a provider can mark itself root_only and can declare keepout ranges the framework refuses to read, so a region missing from a hexdump is not always missing from the device.

Check what your own board has

All of this is inspectable on a running system. Start with the framework itself:

raghu@techveda.org:~$ zcat /proc/config.gz | grep -E "CONFIG_NVMEM(_SYSFS|_LAYOUTS)?="
CONFIG_NVMEM=y
CONFIG_NVMEM_SYSFS=y
CONFIG_NVMEM_LAYOUTS=y

CONFIG_NVMEM_SYSFS is default y, so it is usually on unless someone trimmed the configuration; with no /proc/config.gz, grep the .config in your build tree. Then list the providers, one directory per registered nvmem device, named by the provider driver:

raghu@techveda.org:~$ ls /sys/bus/nvmem/devices/

Read one, remembering that a provider with root_only set needs root:

raghu@techveda.org:~$ sudo hexdump -C /sys/bus/nvmem/devices/imx-ocotp0/nvmem | head -4

On 6.12 and later, look for the per-cell files. Each name is the cell name, an @, the offset, a comma and the bit offset, in hex:

raghu@techveda.org:~$ ls /sys/bus/nvmem/devices/*/cells/
mac-address@88,0

To see what your own device tree already wires up, search the live tree, not the source:

raghu@techveda.org:~$ find /proc/device-tree -name nvmem-cell-names
/proc/device-tree/soc/aips-bus@2100000/ethernet@2188000/nvmem-cell-names
raghu@techveda.org:~$ strings /proc/device-tree/soc/aips-bus@2100000/ethernet@2188000/nvmem-cell-names
mac-address

If a consumer is waiting on a provider, the deferred-probe list says so, given debugfs is mounted. And before relying on a cell, check that the driver reading it honours a deferral:

raghu@techveda.org:~$ sudo cat /sys/kernel/debug/devices_deferred
raghu@techveda.org:~$ grep -n EPROBE_DEFER drivers/net/ethernet/freescale/enetc/enetc_pf.c

Reading a subsystem in that order โ€” binding, then core, then the one consumer you care about โ€” is a habit that works for the rest of the kernel too, and it is how we approach driver work in the Linux device drivers course.

Key takeaways

  • An nvmem cell is a named window into a provider’s flat byte array, described in the device tree and read by the driver that needs it.
  • The provider deliberately knows nothing about the meaning of its bytes. That keeps one driver per silicon block and pushes the board-specific layout into the board file.
  • Ordering is handled by __nvmem_device_get() returning -EPROBE_DEFER and the existing deferred-probe retry, not by new machinery.
  • For a MAC address the wiring is two device tree properties, because of_get_mac_address() already looks for a cell named mac-address, in every longterm kernel from 5.10 onward.
  • The biggest risk is silence: a consumer that discards -EPROBE_DEFER falls back to a random or bootloader address and logs nothing that looks wrong. Bit cells arrive right-aligned and masked, and per-cell sysfs files start at 6.12.
Was this worth your time?

Frequently asked questions

Does using nvmem cells mean patching a kernel driver for each new board?
No. The provider driver belongs to the silicon block, such as an on-chip fuse array or an I2C EEPROM part, and it does not describe the layout. The cells are defined in the device tree, so a new board that stores its data at different offsets needs device tree changes only.

Which kernel version do I need?
The consumer API and the device tree wiring are present in every longterm kernel from 5.10 onward, including the MAC address lookup. Per-cell post-processing needs 6.6, and per-cell sysfs files need 6.12.

What happens if the EEPROM driver probes after the driver that needs the cell?
The cell lookup returns -EPROBE_DEFER and the consumer is retried once more devices have registered. This only works if the consumer propagates that error rather than treating it as a missing value, and several drivers do not.

Can I read a single nvmem cell from a shell script?
On 6.12 and later, yes, from the per-cell files under the provider’s cells directory in sysfs. On earlier kernels only the whole device is exposed as one binary file, so the script has to know the offset itself.

Can I correct a value I wrote into an eFuse by mistake?
No. On OTP and eFuse providers a programmed bit cannot be cleared, so a wrong value is permanent for that unit. This is a hardware property, not a restriction the framework imposes.

Further reading

RB
Raghu Bharadwaj

Founder, TECH VEDA โ€” 20+ years teaching the Linux kernel, device drivers and embedded systems.

Follow on LinkedIn

Get new posts by email

Kernel, embedded Linux and AI-era engineering โ€” a few sharp reads a month. No spam.

We email occasionally and never share your address.