[PATCH v2 01/58] nvmem: retain provider context through final device release
From: James Hilliard
Date: Tue Nov 10 2026 - 02:46:24 EST
An NVMEM provider can keep its read/write context in an object separate
from its parent device. Retaining the parent does not keep that object
alive, and nvmem_unregister() can return while consumers still hold the
provider. A device reference can also delay release after the last
consumer is gone.
Add optional paired callbacks to take a reference to the private context
before publishing the provider and drop it from the final device release
callback. Balance the reference on registration errors as well. Require
both callbacks together. Pin their module before taking the context
reference and release it only after the final put callback returns; the
context callbacks can belong to a different module than the provider.
MTD OTP providers need this ownership because they are children of the
physical flash device, while their callback context is the separately
allocated MTD master.
Cc: Srinivas Kandagatla <srini@xxxxxxxxxx>
Cc: Tze Yee Ng <tze.yee.ng@xxxxxxxxxx>
Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/nvmem/core.c | 18 +++++++++++++++++-
drivers/nvmem/internals.h | 2 ++
include/linux/nvmem-provider.h | 12 ++++++++++++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0556d140170a..8846354ae063 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -541,6 +541,9 @@ static void nvmem_release(struct device *dev)
ida_free(&nvmem_ida, nvmem->id);
gpiod_put(nvmem->wp_gpio);
+ if (nvmem->ops->priv_put)
+ nvmem->ops->priv_put(nvmem->priv);
+ module_put(nvmem->ops->priv_owner);
kfree(nvmem->ops);
kfree(nvmem);
}
@@ -896,6 +899,9 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (!config->reg_read && !config->reg_write)
return ERR_PTR(-EINVAL);
+ if (!!config->priv_get != !!config->priv_put)
+ return ERR_PTR(-EINVAL);
+
nvmem = kzalloc_obj(*nvmem);
if (!nvmem)
return ERR_PTR(-ENOMEM);
@@ -922,6 +928,17 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
device_initialize(&nvmem->dev);
+ nvmem->priv = config->priv;
+ if (config->priv_get) {
+ if (!try_module_get(config->priv_owner)) {
+ rval = -ENODEV;
+ goto err_put_device;
+ }
+ ops->priv_owner = config->priv_owner;
+ config->priv_get(config->priv);
+ ops->priv_put = config->priv_put;
+ }
+
if (!config->ignore_wp)
nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp",
GPIOD_OUT_HIGH);
@@ -945,7 +962,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->word_size = config->word_size ?: 1;
nvmem->size = config->size;
nvmem->root_only = config->root_only;
- nvmem->priv = config->priv;
nvmem->type = config->type;
nvmem->keepout = config->keepout;
nvmem->nkeepout = config->nkeepout;
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4e610deeaa7b..21e26b73c160 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -11,6 +11,8 @@
struct nvmem_operations {
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
+ void (*priv_put)(void *priv);
+ struct module *priv_owner;
};
struct nvmem_device {
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 6063fe5b7784..b674774ee156 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -98,6 +98,9 @@ struct nvmem_cell_info {
* @word_size: Minimum read/write access granularity.
* @stride: Minimum read/write access stride.
* @priv: User context passed to read/write callbacks.
+ * @priv_get: Optional callback taking a reference to @priv before publication.
+ * @priv_put: Matching callback releasing @priv at final device release.
+ * @priv_owner: Module containing @priv_get and @priv_put, or NULL if built-in.
* @ignore_wp: Write Protect pin is managed by the provider.
* @layout: Fixed layout associated with this nvmem device.
*
@@ -107,6 +110,12 @@ struct nvmem_cell_info {
*
* Note: Specifying name and setting id to -1 implies a unique device
* whose name is provided as-is (kept unaltered).
+ *
+ * Note: @priv_get and @priv_put must be supplied together. Each @priv_get
+ * call is balanced by one @priv_put, including on registration failure.
+ * The put can run after nvmem_unregister() returns, when consumers and
+ * device references have gone away. The core holds @priv_owner until
+ * @priv_put returns, independently of the provider's @owner.
*/
struct nvmem_config {
struct device *dev;
@@ -132,6 +141,9 @@ struct nvmem_config {
int word_size;
int stride;
void *priv;
+ void (*priv_get)(void *priv);
+ void (*priv_put)(void *priv);
+ struct module *priv_owner;
/* To be only used by old driver/misc/eeprom drivers */
bool compat;
struct device *base_dev;
--
2.53.0