Re: [PATCH v4 4/8] block: implement NVMEM provider
Bartosz Golaszewski <[email protected]> Tue, 9 Jun 2026 04:52:39 -0400
| Newsgroups | org.infradead.lists.ath10k,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-block,org.kernel.vger.linux-bluetooth,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-mmc,org.kernel.vger.linux-wireless,org.kernel.vger.netdev |
|---|---|
| Message-ID | <CAMRc=MfuiPMkSm=-G6VEJpqKhos0TD_pf0ScBGqfwLHT0uk8yQ@mail.gmail.com> |
On Tue, 9 Jun 2026 09:52:29 +0200, Loic Poulain <[email protected]> said: > From: Daniel Golle <[email protected]> > > On embedded devices using an eMMC it is common that one or more partitions > on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM > data. Allow referencing the partition in device tree for the kernel and > Wi-Fi drivers accessing it via the NVMEM layer. > > Signed-off-by: Daniel Golle <[email protected]> > Co-developed-by: Loic Poulain <[email protected]> > Signed-off-by: Loic Poulain <[email protected]> > --- > block/Kconfig | 9 +++++ > block/Makefile | 1 + > block/blk-nvmem.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 124 insertions(+) > > diff --git a/block/Kconfig b/block/Kconfig > index 15027963472d7b40e27b9097a5993c457b5b3054..0b33747e16dc33473683706f75c92bdf8b648f7c 100644 > --- a/block/Kconfig > +++ b/block/Kconfig > @@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK > by falling back to the kernel crypto API when inline > encryption hardware is not present. > > +config BLK_NVMEM > + bool "Block device NVMEM provider" > + depends on OF > + depends on NVMEM > + help > + Allow block devices (or partitions) to act as NVMEM providers, > + typically used with eMMC to store MAC addresses or Wi-Fi > + calibration data on embedded devices. > + > source "block/partitions/Kconfig" > > config BLK_PM > diff --git a/block/Makefile b/block/Makefile > index 7dce2e44276c4274c11a0a61121c83d9c43d6e0c..d7ac389e71902bc091a8800ea266190a43b3e63d 100644 > --- a/block/Makefile > +++ b/block/Makefile > @@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \ > blk-crypto-sysfs.o > obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o > obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED) += holder.o > +obj-$(CONFIG_BLK_NVMEM) += blk-nvmem.o > diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c > new file mode 100644 > index 0000000000000000000000000000000000000000..a6e62fa98675ee9bcb9c7035a611b5a573ab9091 > --- /dev/null > +++ b/block/blk-nvmem.c > @@ -0,0 +1,114 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * block device NVMEM provider > + * > + * Copyright (c) 2024 Daniel Golle <[email protected]> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. > + * > + * Useful on devices using a partition on an eMMC for MAC addresses or > + * Wi-Fi calibration EEPROM data. > + */ > + > +#include <linux/file.h> > +#include <linux/nvmem-provider.h> > +#include <linux/nvmem-consumer.h> > +#include <linux/of.h> > +#include <linux/pagemap.h> > +#include <linux/property.h> > + > +#include "blk.h" > + > +static int blk_nvmem_reg_read(void *priv, unsigned int from, > + void *val, size_t bytes) > +{ > + blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES; > + dev_t devt = (dev_t)(uintptr_t)priv; > + size_t bytes_left = bytes; > + loff_t pos = from; > + int ret = 0; > + > + struct file *bdev_file __free(fput) = bdev_file_open_by_dev(devt, mode, priv, NULL); > + if (IS_ERR(bdev_file)) > + return PTR_ERR(bdev_file); > + > + while (bytes_left) { > + pgoff_t f_index = pos >> PAGE_SHIFT; > + struct folio *folio; > + size_t folio_off; > + size_t to_read; > + > + folio = read_mapping_folio(bdev_file->f_mapping, f_index, NULL); > + if (IS_ERR(folio)) { > + ret = PTR_ERR(folio); > + break; > + } > + > + folio_off = offset_in_folio(folio, pos); > + to_read = min(bytes_left, folio_size(folio) - folio_off); > + memcpy_from_folio(val, folio, folio_off, to_read); > + pos += to_read; > + bytes_left -= to_read; > + val += to_read; > + folio_put(folio); > + } > + > + return ret; > +} > + > +static int blk_nvmem_register(struct device *dev) > +{ > + struct block_device *bdev = dev_to_bdev(dev); > + struct nvmem_config config = {}; > + > + /* skip devices which do not have a device tree node */ > + if (!dev_of_node(dev)) > + return 0; > + > + /* skip devices without an nvmem layout defined */ > + struct device_node *child __free(device_node) = > + of_get_child_by_name(dev_of_node(dev), "nvmem-layout"); > + if (!child) > + return 0; > + > + /* > + * skip block device too large to be represented as NVMEM devices, > + * the NVMEM reg_read callback uses an unsigned int offset > + */ > + if (bdev_nr_bytes(bdev) > UINT_MAX) { > + dev_warn(dev, "block device too large to be an NVMEM provider\n"); > + return -ENODEV; Wait, I must have suggested -ENODEV here on too little coffee. This callback is called from device_add(), not when the device is bound so it's not the same thing as returning -ENODEV from probe(). On the other hand, we don't want to not provide the block device just because someone added a DT property on one that's too big. I'd say: warn, but return 0. Does it make sense? > + } > + > + config.id = NVMEM_DEVID_NONE; > + config.dev = dev; > + config.name = dev_name(dev); > + config.owner = THIS_MODULE; > + config.priv = (void *)(uintptr_t)dev->devt; > + config.reg_read = blk_nvmem_reg_read; > + config.size = bdev_nr_bytes(bdev); > + config.word_size = 1; > + config.stride = 1; > + config.read_only = true; > + config.root_only = true; > + config.ignore_wp = true; > + config.of_node = to_of_node(dev->fwnode); > + > + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config)); And that was a wrong suggestion on my part too because I was under the impression that we're in the probe() path, not device_add(). You can't use devres here as the device at this point is not yet bound and may never be. Which leads me to the second point: this is not the moment to add the nvmem provider. This should happen at or after probe(). Once nvmem_register() returns, you have a visible nvmem resource but nothing backing it in the block layer. Either do this in block core when registering a new device or schedule a notifier here for the BUS_NOTIFY_BOUND_DRIVER event and do it in the notifier callback. Sorry, I should have paid more attention, I forgot how the class interface works. > +} > + > +static struct class_interface blk_nvmem_bus_interface __refdata = { > + .class = &block_class, > + .add_dev = &blk_nvmem_register, > +}; > + > +static int __init blk_nvmem_init(void) > +{ > + int ret; > + > + ret = class_interface_register(&blk_nvmem_bus_interface); > + if (ret) > + return ret; > + > + return 0; > +} > +device_initcall(blk_nvmem_init); > > -- > 2.34.1 > > Bart