[PATCH 02/58] nvmem: retain provider context through final device release
James Hilliard
james.hilliard1 at gmail.com
Sat Nov 7 02:15:59 PST 2026
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 and document the associated module lifetime
requirement.
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.
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
drivers/nvmem/core.c | 12 +++++++++++-
drivers/nvmem/internals.h | 1 +
include/linux/nvmem-provider.h | 10 ++++++++++
3 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0556d140170a..851fa5cabecb 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -541,6 +541,8 @@ 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);
kfree(nvmem->ops);
kfree(nvmem);
}
@@ -896,6 +898,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 +927,12 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
device_initialize(&nvmem->dev);
+ nvmem->priv = config->priv;
+ if (config->priv_get) {
+ 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 +956,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..dcf5d81ca1b9 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -11,6 +11,7 @@
struct nvmem_operations {
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
+ void (*priv_put)(void *priv);
};
struct nvmem_device {
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 6063fe5b7784..03e8522bab17 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -98,6 +98,8 @@ 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.
* @ignore_wp: Write Protect pin is managed by the provider.
* @layout: Fixed layout associated with this nvmem device.
*
@@ -107,6 +109,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. These callbacks must also retain any
+ * module needed to execute @priv_put until it returns.
*/
struct nvmem_config {
struct device *dev;
@@ -132,6 +140,8 @@ struct nvmem_config {
int word_size;
int stride;
void *priv;
+ void (*priv_get)(void *priv);
+ void (*priv_put)(void *priv);
/* To be only used by old driver/misc/eeprom drivers */
bool compat;
struct device *base_dev;
--
2.53.0
More information about the linux-mtd
mailing list