* [PATCH 01/58] mtd: clear OTP provider pointers after unregistering them
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
@ 2026-11-07 10:15 ` James Hilliard
2026-11-07 10:15 ` [PATCH 02/58] nvmem: retain provider context through final device release James Hilliard
` (22 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:15 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard, stable
If factory OTP discovery or registration fails after user OTP was
registered, mtd_otp_nvmem_add() unregisters the user provider but leaves
its pointer set. mtd_device_parse_register() then unregisters it again
while unwinding the error, accessing a freed provider or dropping a
reference still owned by a consumer.
The same stale pointer survives when the factory OTP error is
-EOPNOTSUPP, which mtd_otp_nvmem_add() treats as success. In that case
mtd_device_unregister() can release the provider a second time later.
Centralize removal of the OTP providers and clear each pointer after
unregistering it so subsequent cleanup cannot repeat the put.
Fixes: e0489f6e221f ("mtd: core: fix error path for nvmem provider")
Cc: stable@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/mtdcore.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 8efa2d88556e..558a605ba749 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1016,6 +1016,14 @@ static int mtd_nvmem_fact_otp_reg_read(void *priv, unsigned int offset,
return retlen == bytes ? 0 : -EIO;
}
+static void mtd_otp_nvmem_remove(struct mtd_info *mtd)
+{
+ nvmem_unregister(mtd->otp_user_nvmem);
+ mtd->otp_user_nvmem = NULL;
+ nvmem_unregister(mtd->otp_factory_nvmem);
+ mtd->otp_factory_nvmem = NULL;
+}
+
static int mtd_otp_nvmem_add(struct mtd_info *mtd)
{
struct device *dev = mtd->dev.parent;
@@ -1082,7 +1090,7 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd)
return 0;
err:
- nvmem_unregister(mtd->otp_user_nvmem);
+ mtd_otp_nvmem_remove(mtd);
/* Don't report error if OTP is not supported. */
if (err == -EOPNOTSUPP)
return 0;
@@ -1180,10 +1188,8 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
}
out:
- if (ret) {
- nvmem_unregister(mtd->otp_user_nvmem);
- nvmem_unregister(mtd->otp_factory_nvmem);
- }
+ if (ret)
+ mtd_otp_nvmem_remove(mtd);
if (ret && device_is_registered(&mtd->dev)) {
err = del_mtd_device(mtd);
@@ -1210,8 +1216,7 @@ int mtd_device_unregister(struct mtd_info *master)
memset(&master->reboot_notifier, 0, sizeof(master->reboot_notifier));
}
- nvmem_unregister(master->otp_user_nvmem);
- nvmem_unregister(master->otp_factory_nvmem);
+ mtd_otp_nvmem_remove(master);
if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
err = mtd_virt_concat_destroy(master);
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 02/58] nvmem: retain provider context through final device release
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-07 10:15 ` [PATCH 01/58] mtd: clear OTP provider pointers after unregistering them James Hilliard
@ 2026-11-07 10:15 ` James Hilliard
2026-11-07 10:16 ` [PATCH 03/58] mtd: add opt-in lifetime ownership for backing objects James Hilliard
` (21 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:15 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
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@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
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 03/58] mtd: add opt-in lifetime ownership for backing objects
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-07 10:15 ` [PATCH 01/58] mtd: clear OTP provider pointers after unregistering them James Hilliard
2026-11-07 10:15 ` [PATCH 02/58] nvmem: retain provider context through final device release James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 04/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
` (20 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
MTD references retain registered nodes, but do not retain the backing
object allocated by a controller driver. Keeping a partition open across
driver unbind can therefore leave its master and driver metadata freed.
Device references held outside get_mtd_device(), such as NVMEM
consumers, also need the backing object and the partition ancestry to
remain valid.
Add an optional master lifetime initialized by mtd_set_release(). The
driver owns the initial reference. Each registered node and acquired MTD
handle holds another reference. The node reference lasts through final
device release, and nested partitions retain their parent device until
then. mtd_put_master() drops the driver's reference after hardware has
been disconnected and the MTD devices unregistered.
Give OTP NVMEM providers their own backing-object references through the
NVMEM context callbacks. They are parented to the physical device rather
than the MTD node, so the node reference does not cover them. Keep these
references through final NVMEM device release, including after failed MTD
registration, and use the backing driver's module as the consumer owner.
Pin the release callback's module when the driver drops its reference.
If the module is initializing or unloading, wait for the remaining
references instead: failed module initialization can discard module text
despite reference pins, and unload cannot acquire a new pin. Keep the
MTD core module pinned through its device and OTP release callbacks too.
Distinguish registration from retained references so an unregistered
node cannot be opened again. Reference a registered master even when
MTD_PARTITIONED_MASTER is disabled: an unpartitioned device is still
registered in that configuration. Remove the lookup entry under
mtd_table_mutex at the last MTD put, but reserve its number until final
device release. A retained NVMEM provider still uses that number in its
device name, so reusing it earlier would make rebind fail with -EEXIST.
On registration failure, wait for the overridden device release callback
before returning ownership to the caller. This also covers delayed
kobject release, when an immediate caller free would be premature.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/mtdcore.c | 195 +++++++++++++++++++++++++++++++++++++++++-------
include/linux/mtd/mtd.h | 16 ++++
2 files changed, 184 insertions(+), 27 deletions(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 558a605ba749..42c06a02cd99 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -31,6 +31,7 @@
#include <linux/nvmem-provider.h>
#include <linux/root_dev.h>
#include <linux/error-injection.h>
+#include <linux/completion.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
@@ -70,6 +71,7 @@ static struct class mtd_class = {
};
static DEFINE_IDR(mtd_idr);
+static DEFINE_IDA(mtd_ids);
/* These are exported solely for the purpose of mtd_blkdevs.c. You
should not use them for _anything_ else */
@@ -78,7 +80,15 @@ EXPORT_SYMBOL_GPL(mtd_table_mutex);
struct mtd_info *__mtd_next_device(int i)
{
- return idr_get_next(&mtd_idr, &i);
+ struct mtd_info *mtd;
+
+ while ((mtd = idr_get_next(&mtd_idr, &i))) {
+ if (mtd->registered)
+ return mtd;
+ i++;
+ }
+
+ return NULL;
}
EXPORT_SYMBOL_GPL(__mtd_next_device);
@@ -87,37 +97,118 @@ static LIST_HEAD(mtd_notifiers);
#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
-/* REVISIT once MTD uses the driver model better, whoever allocates
- * the mtd_info will probably want to use the release() hook...
+static void mtd_master_release(struct kref *ref)
+{
+ struct mtd_info *master = container_of(ref, struct mtd_info,
+ master.life_refcnt);
+ struct completion *done = master->master.release_done;
+ struct module *owner = master->master.release_owner;
+
+ master->master.release(master);
+ if (done)
+ complete(done);
+ else
+ module_put(owner);
+}
+
+static void mtd_get_lifetime(struct mtd_info *master)
+{
+ if (master->master.release)
+ kref_get(&master->master.life_refcnt);
+}
+
+static void mtd_put_lifetime(struct mtd_info *master)
+{
+ if (master->master.release)
+ kref_put(&master->master.life_refcnt, mtd_master_release);
+}
+
+/**
+ * mtd_set_release - initialize the lifetime of a dynamically allocated master
+ * @master: master MTD device
+ * @release: free the backing object and its retained metadata
+ *
+ * Call once before registration. The driver owns the initial reference and
+ * drops it with mtd_put_master() after unregistering and disconnecting hardware.
+ * Registered MTD devices and acquired handles retain the backing object too.
+ * The release callback must not access resources released by driver unbind.
*/
+void mtd_set_release(struct mtd_info *master,
+ void (*release)(struct mtd_info *mtd))
+{
+ kref_init(&master->master.life_refcnt);
+ master->master.release = release;
+}
+EXPORT_SYMBOL_GPL(mtd_set_release);
+
+/**
+ * mtd_put_master - drop the driver's initial lifetime reference
+ * @master: master initialized by mtd_set_release()
+ *
+ * Hardware access must already be disconnected and drained. Unbind may return
+ * while handles retain metadata. Keep the release callback's module loaded
+ * until that metadata is freed. During module unload there can be no acquired
+ * handles, since they pin the module; wait for remaining device references
+ * instead of scheduling a callback into the departing module. Also wait during
+ * module initialization, which may fail and discard the module despite pins.
+ */
+void mtd_put_master(struct mtd_info *master)
+{
+ DECLARE_COMPLETION_ONSTACK(done);
+
+ if ((!master->owner || READ_ONCE(master->owner->state) == MODULE_STATE_LIVE) &&
+ try_module_get(master->owner)) {
+ master->master.release_owner = master->owner;
+ mtd_put_lifetime(master);
+ } else {
+ master->master.release_done = &done;
+ mtd_put_lifetime(master);
+ wait_for_completion(&done);
+ }
+}
+EXPORT_SYMBOL_GPL(mtd_put_master);
+
static void mtd_release(struct device *dev)
{
struct mtd_info *mtd = dev_get_drvdata(dev);
- dev_t index = MTD_DEVT(mtd->index);
+ struct mtd_info *master = mtd->ref_master;
+ struct mtd_info *parent = mtd->parent;
- idr_remove(&mtd_idr, mtd->index);
+ /* Retained NVMEM providers use the MTD number in their device name. */
+ ida_free(&mtd_ids, mtd->index);
of_node_put(mtd_get_of_node(mtd));
if (mtd_is_partition(mtd))
release_mtd_partition(mtd);
- /* remove /dev/mtdXro node */
- device_destroy(&mtd_class, index + 1);
+ if (master) {
+ /* Nested partitions can outlive device_del() of their parents. */
+ if (parent && parent != master)
+ put_device(&parent->dev);
+ mtd_put_lifetime(master);
+ }
+ module_put(THIS_MODULE);
}
/*
- * No-op device release used in add_mtd_device() error paths.
+ * Hand failed registrations back to their caller only after device release.
* Prevents mtd_release() from being called via device_release(),
* which would free the mtd_info that the caller still manages.
*/
-static void mtd_dev_release_nop(struct device *dev)
+static void mtd_dev_release_done(struct device *dev)
{
+ complete(dev_get_drvdata(dev));
}
static void mtd_device_release(struct kref *kref)
{
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
bool is_partition = mtd_is_partition(mtd);
+ bool managed = mtd->ref_master;
+
+ /* The table mutex is held by the registration or handle put path. */
+ idr_remove(&mtd_idr, mtd->index);
+ device_destroy(&mtd_class, MTD_DEVT(mtd->index) + 1);
debugfs_remove_recursive(mtd->dbg.dfs_dir);
@@ -131,10 +222,8 @@ static void mtd_device_release(struct kref *kref)
* Should not be done for partition,
* as it was already destroyed in device_unregister().
*/
- if (!is_partition)
+ if (!is_partition && !managed)
memset(&mtd->dev, 0, sizeof(mtd->dev));
-
- module_put(THIS_MODULE);
}
#define MTD_DEVICE_ATTR_RO(name) \
@@ -612,7 +701,7 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
config.id = NVMEM_DEVID_NONE;
config.dev = &mtd->dev;
config.name = dev_name(&mtd->dev);
- config.owner = THIS_MODULE;
+ config.owner = mtd_get_master(mtd)->owner;
config.add_legacy_fixed_of_cells = of_device_is_compatible(node, "nvmem-cells");
config.reg_read = mtd_nvmem_reg_read;
config.size = mtd->size;
@@ -702,10 +791,13 @@ static void mtd_check_of_node(struct mtd_info *mtd)
int add_mtd_device(struct mtd_info *mtd)
{
+ DECLARE_COMPLETION_ONSTACK(released);
struct device_node *np = mtd_get_of_node(mtd);
struct mtd_info *master = mtd_get_master(mtd);
struct mtd_notifier *not;
int i, error, ofidx;
+ u32 id;
+ bool wait_for_release = false;
/*
* May occur, for instance, on buggy drivers which call
@@ -748,16 +840,28 @@ int add_mtd_device(struct mtd_info *mtd)
if (np)
ofidx = of_alias_get_id(np, "mtd");
if (ofidx >= 0)
- i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL);
+ i = ida_alloc_range(&mtd_ids, ofidx, ofidx, GFP_KERNEL);
else
- i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
+ i = ida_alloc(&mtd_ids, GFP_KERNEL);
if (i < 0) {
error = i;
goto fail_locked;
}
+ id = i;
+ error = idr_alloc_u32(&mtd_idr, mtd, &id, id, GFP_KERNEL);
+ if (error) {
+ ida_free(&mtd_ids, i);
+ goto fail_locked;
+ }
mtd->index = i;
kref_init(&mtd->refcnt);
+ if (master->master.release) {
+ mtd_get_lifetime(master);
+ mtd->ref_master = master;
+ if (mtd->parent && mtd->parent != master)
+ get_device(&mtd->parent->dev);
+ }
/* default value if not set by driver */
if (mtd->bitflip_threshold == 0)
@@ -821,7 +925,10 @@ int add_mtd_device(struct mtd_info *mtd)
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
"mtd%dro", i);
+ /* Keep the release callback loaded until the final device reference. */
+ __module_get(THIS_MODULE);
pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
+ mtd->registered = true;
/* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */
list_for_each_entry(not, &mtd_notifiers, list)
@@ -839,29 +946,38 @@ int add_mtd_device(struct mtd_info *mtd)
}
}
- /* We _know_ we aren't being removed, because
- our caller is still holding us here. So none
- of this try_ nonsense, and no bitching about it
- either. :) */
- __module_get(THIS_MODULE);
return 0;
fail_nvmem_add:
device_del(&mtd->dev);
fail_added:
/*
- * Clear type and set nop release to prevent mtd_release() ->
+ * Clear type and override release to prevent mtd_release() ->
* release_mtd_partition() -> free_partition() from freeing mtd.
* The caller handles cleanup on failure.
*/
mtd->dev.type = NULL;
- mtd->dev.release = mtd_dev_release_nop;
+ mtd->dev.release = mtd_dev_release_done;
+ dev_set_drvdata(&mtd->dev, &released);
+ wait_for_release = true;
put_device(&mtd->dev);
of_node_put(mtd_get_of_node(mtd));
fail_devname:
idr_remove(&mtd_idr, i);
+ ida_free(&mtd_ids, i);
+ if (mtd->ref_master) {
+ mtd->ref_master = NULL;
+ if (mtd->parent && mtd->parent != master)
+ put_device(&mtd->parent->dev);
+ mtd_put_lifetime(master);
+ }
fail_locked:
mutex_unlock(&mtd_table_mutex);
+ if (wait_for_release) {
+ wait_for_completion(&released);
+ mtd->dev.release = NULL;
+ dev_set_drvdata(&mtd->dev, mtd);
+ }
return error;
}
@@ -882,7 +998,7 @@ int del_mtd_device(struct mtd_info *mtd)
mutex_lock(&mtd_table_mutex);
- if (idr_find(&mtd_idr, mtd->index) != mtd) {
+ if (idr_find(&mtd_idr, mtd->index) != mtd || !mtd->registered) {
ret = -ENODEV;
goto out_error;
}
@@ -892,6 +1008,7 @@ int del_mtd_device(struct mtd_info *mtd)
list_for_each_entry(not, &mtd_notifiers, list)
not->remove(mtd);
+ mtd->registered = false;
kref_put(&mtd->refcnt, mtd_device_release);
ret = 0;
@@ -952,6 +1069,18 @@ static ssize_t mtd_otp_size(struct mtd_info *mtd, bool is_user)
return ret == -ENODATA ? 0 : ret;
}
+static void mtd_otp_nvmem_get(void *priv)
+{
+ mtd_get_lifetime(priv);
+ __module_get(THIS_MODULE);
+}
+
+static void mtd_otp_nvmem_put(void *priv)
+{
+ mtd_put_lifetime(priv);
+ module_put(THIS_MODULE);
+}
+
static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
const char *compatible,
int size,
@@ -968,7 +1097,7 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
config.dev = mtd->dev.parent;
config.name = compatible;
config.id = NVMEM_DEVID_AUTO;
- config.owner = THIS_MODULE;
+ config.owner = mtd->owner;
config.add_legacy_fixed_of_cells = !mtd_type_is_nand(mtd);
config.type = NVMEM_TYPE_OTP;
config.root_only = true;
@@ -977,6 +1106,10 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
config.size = size;
config.of_node = np;
config.priv = mtd;
+ if (mtd->master.release) {
+ config.priv_get = mtd_otp_nvmem_get;
+ config.priv_put = mtd_otp_nvmem_put;
+ }
nvmem = nvmem_register(&config);
/* Just ignore if there is no NVMEM support in the kernel */
@@ -1336,6 +1469,9 @@ int __get_mtd_device(struct mtd_info *mtd)
struct mtd_info *master = mtd_get_master(mtd);
int err;
+ if (!mtd->registered)
+ return -ENODEV;
+
if (master->_get_device) {
err = master->_get_device(mtd);
if (err)
@@ -1348,13 +1484,14 @@ int __get_mtd_device(struct mtd_info *mtd)
return -ENODEV;
}
+ mtd_get_lifetime(master);
while (mtd) {
if (mtd != master)
kref_get(&mtd->refcnt);
mtd = mtd->parent;
}
- if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+ if (master->dev.type)
kref_get(&master->refcnt);
return 0;
@@ -1441,6 +1578,7 @@ void __put_mtd_device(struct mtd_info *mtd)
struct mtd_info *master = mtd_get_master(mtd);
/* master can be freed in _put_device */
struct module *owner = master->owner;
+ bool managed = master->master.release;
while (mtd) {
/* kref_put() can relese mtd, so keep a reference mtd->parent */
@@ -1451,12 +1589,14 @@ void __put_mtd_device(struct mtd_info *mtd)
mtd = parent;
}
- if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+ if (master->dev.type)
kref_put(&master->refcnt, mtd_device_release);
- /* must be the last use of master */
+ /* Legacy _put_device() callbacks can free an unmanaged master. */
if (master->_put_device)
master->_put_device(master);
+ if (managed)
+ mtd_put_lifetime(master);
/* _put_device() runs code from @owner, so drop the module last */
module_put(owner);
@@ -2673,6 +2813,7 @@ static void __exit cleanup_mtd(void)
bdi_unregister(mtd_bdi);
bdi_put(mtd_bdi);
idr_destroy(&mtd_idr);
+ ida_destroy(&mtd_ids);
}
module_init(init_mtd);
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 8d10d9d2e830..5a1bdae00665 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -187,6 +187,7 @@ struct mtd_pairing_scheme {
};
struct module; /* only needed for owner field in mtd_info */
+struct completion;
/**
* struct mtd_debug_info - debugging information for an MTD device.
@@ -224,6 +225,10 @@ struct mtd_part {
* not only the master partition list, but also all
* sub-partitions.
* @suspended: set to 1 when the device is suspended, 0 otherwise
+ * @life_refcnt: optional lifetime reference count for the backing object
+ * @release: free the backing object after its last lifetime reference
+ * @release_owner: module reference held during deferred release
+ * @release_done: completion used when the owner module is already unloading
*
* This struct is embedded in mtd_info and contains master-specific
* properties/fields. The master is the root MTD device from the MTD partition
@@ -233,6 +238,10 @@ struct mtd_master {
struct mutex partitions_lock;
struct mutex chrdev_lock;
unsigned int suspended : 1;
+ struct kref life_refcnt;
+ void (*release)(struct mtd_info *mtd);
+ struct module *release_owner;
+ struct completion *release_done;
};
struct mtd_info {
@@ -380,6 +389,10 @@ struct mtd_info {
struct module *owner;
struct device dev;
struct kref refcnt;
+ /* Protected by mtd_table_mutex; false after del_mtd_device(). */
+ bool registered;
+ /* Lifetime reference held until the device release callback. */
+ struct mtd_info *ref_master;
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;
struct nvmem_device *otp_user_nvmem;
@@ -681,6 +694,9 @@ extern int mtd_device_parse_register(struct mtd_info *mtd,
#define mtd_device_register(master, parts, nr_parts) \
mtd_device_parse_register(master, NULL, NULL, parts, nr_parts)
extern int mtd_device_unregister(struct mtd_info *master);
+void mtd_set_release(struct mtd_info *master,
+ void (*release)(struct mtd_info *mtd));
+void mtd_put_master(struct mtd_info *master);
extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
extern int __get_mtd_device(struct mtd_info *mtd);
extern void __put_mtd_device(struct mtd_info *mtd);
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 04/58] mtd: rawnand: separate hardware disconnection from cleanup
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (2 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 03/58] mtd: add opt-in lifetime ownership for backing objects James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 05/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
` (19 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
A retained MTD handle can still enter NAND operations after its
controller is unbound. Closing a writable character device also calls
nand_sync(), which takes the chip and controller locks. Freeing their
storage during remove is therefore unsafe even without another explicit
read or write.
Add nand_disconnect() to serialize disconnection with regular operations
under the chip and controller locks. Make nand_get_device() reject a
disconnected chip and propagate that error through its callers. A sync
after disconnection becomes a no-op. Suspend and resume also honor the
disconnected state.
Unregister MTD devices first so removal notifiers can finish their I/O,
then disconnect before releasing controller resources. Wake any
remaining suspended waiters when disconnecting. Panic writes also check
the disconnected flag; their notifier clients must be drained by MTD
unregistration before controller resources are released.
Keep nand_cleanup() separate. Controllers adopting this interface must
retain the chip, controller lock storage and NAND metadata until their
last MTD and device references are gone.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/nand_base.c | 76 +++++++++++++++++++++++++++++++++-------
include/linux/mtd/rawnand.h | 9 +++--
2 files changed, 69 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index a5b278ab9384..8f7d2571e8d9 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -336,21 +336,47 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
*
* Lock the device and its controller for exclusive access
*/
-static void nand_get_device(struct nand_chip *chip)
+static int nand_get_device(struct nand_chip *chip)
{
/* Wait until the device is resumed. */
while (1) {
mutex_lock(&chip->lock);
+ if (chip->disconnected) {
+ mutex_unlock(&chip->lock);
+ return -ENODEV;
+ }
if (!chip->suspended) {
mutex_lock(&chip->controller->lock);
- return;
+ return 0;
}
mutex_unlock(&chip->lock);
- wait_event(chip->resume_wq, !chip->suspended);
+ wait_event(chip->resume_wq, !READ_ONCE(chip->suspended) ||
+ READ_ONCE(chip->disconnected));
}
}
+/**
+ * nand_disconnect - drain hardware access and reject further operations
+ * @chip: NAND chip object
+ *
+ * Unregister the MTD device first so removal notifiers can finish their I/O.
+ * Call before releasing controller resources. Operations waiting for resume
+ * are woken and fail with -ENODEV.
+ * The chip, controller lock and NAND metadata must remain alive
+ * until existing MTD handles are released; nand_cleanup() is a separate step.
+ */
+void nand_disconnect(struct nand_chip *chip)
+{
+ mutex_lock(&chip->lock);
+ mutex_lock(&chip->controller->lock);
+ WRITE_ONCE(chip->disconnected, true);
+ mutex_unlock(&chip->controller->lock);
+ mutex_unlock(&chip->lock);
+ wake_up_all(&chip->resume_wq);
+}
+EXPORT_SYMBOL_GPL(nand_disconnect);
+
/**
* nand_check_wp - [GENERIC] check if the chip is write protected
* @chip: NAND chip object
@@ -581,7 +607,9 @@ static int nand_block_markbad_lowlevel(struct nand_chip *chip, loff_t ofs)
nand_erase_nand(chip, &einfo, 0);
/* Write bad block marker to OOB */
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
ret = nand_markbad_bbm(chip, ofs);
nand_release_device(chip);
@@ -3879,7 +3907,9 @@ static int nand_read_oob(struct mtd_info *mtd, loff_t from,
ops->mode != MTD_OPS_RAW)
return -ENOTSUPP;
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
old_stats = mtd->ecc_stats;
@@ -4449,6 +4479,9 @@ static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len,
struct mtd_oob_ops ops;
int ret;
+ if (READ_ONCE(chip->disconnected))
+ return -ENODEV;
+
nand_select_target(chip, chipnr);
/* Wait for the device to get ready */
@@ -4479,7 +4512,9 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
ops->retlen = 0;
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
switch (ops->mode) {
case MTD_OPS_PLACE_OOB:
@@ -4539,7 +4574,9 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
return -EIO;
/* Grab the lock and see if the device is available */
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
/* Shift to get first page */
page = (int)(instr->addr >> chip->page_shift);
@@ -4627,7 +4664,8 @@ static void nand_sync(struct mtd_info *mtd)
pr_debug("%s: called\n", __func__);
/* Grab the lock and see if the device is available */
- nand_get_device(chip);
+ if (nand_get_device(chip))
+ return;
/* Release it and go back */
nand_release_device(chip);
}
@@ -4644,7 +4682,9 @@ static int nand_block_isbad(struct mtd_info *mtd, loff_t offs)
int ret;
/* Select the NAND device */
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
nand_select_target(chip, chipnr);
@@ -4688,10 +4728,14 @@ static int nand_suspend(struct mtd_info *mtd)
int ret = 0;
mutex_lock(&chip->lock);
+ if (chip->disconnected) {
+ mutex_unlock(&chip->lock);
+ return -ENODEV;
+ }
if (chip->ops.suspend)
ret = chip->ops.suspend(chip);
if (!ret)
- chip->suspended = 1;
+ WRITE_ONCE(chip->suspended, true);
mutex_unlock(&chip->lock);
return ret;
@@ -4706,10 +4750,12 @@ static void nand_resume(struct mtd_info *mtd)
struct nand_chip *chip = mtd_to_nand(mtd);
scoped_guard(mutex, &chip->lock) {
+ if (chip->disconnected)
+ return;
if (chip->suspended) {
if (chip->ops.resume)
chip->ops.resume(chip);
- chip->suspended = 0;
+ WRITE_ONCE(chip->suspended, false);
} else {
pr_err("%s called for a chip which is not in suspended state\n",
__func__);
@@ -4743,7 +4789,9 @@ static int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
if (!chip->ops.lock_area)
return -ENOTSUPP;
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
ret = chip->ops.lock_area(chip, ofs, len);
nand_release_device(chip);
@@ -4764,7 +4812,9 @@ static int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
if (!chip->ops.unlock_area)
return -ENOTSUPP;
- nand_get_device(chip);
+ ret = nand_get_device(chip);
+ if (ret)
+ return ret;
ret = chip->ops.unlock_area(chip, ofs, len);
nand_release_device(chip);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 5c70e7bd3ed5..90b2169cc745 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -1264,9 +1264,10 @@ struct nand_secure_region {
* @pagecache.page: Page number currently in the cache. -1 means no page is
* currently cached
* @buf_align: Minimum buffer alignment required by a platform
- * @lock: Lock protecting the suspended field. Also used to serialize accesses
- * to the NAND device
+ * @lock: Lock protecting the suspended and disconnected fields. Also serializes
+ * accesses to the NAND device
* @suspended: Set to 1 when the device is suspended, 0 when it's not
+ * @disconnected: Controller resources are unavailable; reject hardware access
* @resume_wq: wait queue to sleep if rawnand is in suspended state.
* @cur_cs: Currently selected target. -1 means no target selected, otherwise we
* should always have cur_cs >= 0 && cur_cs < nanddev_ntargets().
@@ -1326,7 +1327,8 @@ struct nand_chip {
/* Internals */
struct mutex lock;
- unsigned int suspended : 1;
+ bool suspended;
+ bool disconnected;
wait_queue_head_t resume_wq;
int cur_cs;
int read_retries;
@@ -1595,6 +1597,7 @@ void nand_wait_ready(struct nand_chip *chip);
* sucessful nand_scan().
*/
void nand_cleanup(struct nand_chip *chip);
+void nand_disconnect(struct nand_chip *chip);
/*
* External helper for controller drivers that have to implement the WAITRDY
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 05/58] mtd: rawnand: share retained chip and controller ownership
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (3 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 04/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 06/58] mtd: rawnand: macronix: serialize OTP access with NAND operations James Hilliard
` (18 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Controller drivers need to keep chip metadata and serialization locks
alive after unregistering MTD, without retaining their hardware
resources. Share the name, firmware-node and parent-device references,
the final NAND cleanup, and the disconnected-device open check.
Provide reference-counted controller storage with an initial
device-managed reference. Register that reference before managed
hardware resources so their teardown finishes before the controller can
be freed. Each retained chip pins the controller independently, allowing
multi-chip controllers and embedded chip structures to use the same
lifetime rules.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/nand_lifetime.c | 118 +++++++++++++++++++++++++++++++++++
include/linux/mtd/rawnand.h | 12 ++++
3 files changed, 131 insertions(+)
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 619760138d32..7ea275a40fcd 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_MTD_NAND_NUVOTON_MA35) += nuvoton-ma35d1-nand-controller.o
obj-$(CONFIG_MTD_NAND_LOONGSON) += loongson-nand-controller.o
nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
+nand-objs += nand_lifetime.o
nand-objs += nand_onfi.o
nand-objs += nand_jedec.o
nand-objs += nand_amd.o
diff --git a/drivers/mtd/nand/raw/nand_lifetime.c b/drivers/mtd/nand/raw/nand_lifetime.c
new file mode 100644
index 000000000000..9518c9a4bc63
--- /dev/null
+++ b/drivers/mtd/nand/raw/nand_lifetime.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Storage lifetime for disconnected NAND controllers and chips. */
+
+#include <linux/device.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/slab.h>
+
+static void nand_controller_release(struct kref *ref)
+{
+ struct nand_controller *controller;
+
+ controller = container_of(ref, struct nand_controller, refcnt);
+ controller->release(controller);
+}
+
+static void nand_controller_put(void *data)
+{
+ struct nand_controller *controller = data;
+
+ kref_put(&controller->refcnt, nand_controller_release);
+}
+
+/**
+ * devm_nand_controller_init - initialize reference-counted controller storage
+ * @dev: controller device
+ * @controller: controller embedded in driver-owned, non-devm storage
+ * @release: callback freeing that storage, without accessing hardware
+ *
+ * Call before acquiring managed hardware resources. Device teardown drops its
+ * storage reference after releasing those resources. Chips prepared with
+ * nand_set_release() hold additional references until their final MTD put.
+ *
+ * Takes ownership of the storage even on failure.
+ * Return: zero on success or a negative error code.
+ */
+int devm_nand_controller_init(struct device *dev, struct nand_controller *controller,
+ void (*release)(struct nand_controller *controller))
+{
+ nand_controller_init(controller);
+ kref_init(&controller->refcnt);
+ controller->release = release;
+
+ return devm_add_action_or_reset(dev, nand_controller_put, controller);
+}
+EXPORT_SYMBOL_GPL(devm_nand_controller_init);
+
+static int nand_mtd_get_device(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd_get_master(mtd));
+
+ guard(mutex)(&chip->lock);
+ return chip->disconnected ? -ENODEV : 0;
+}
+
+static void nand_mtd_release(struct mtd_info *mtd)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+ struct nand_controller *controller = chip->controller;
+ struct device *parent = mtd->dev.parent;
+ struct device_node *np = nand_get_flash_node(chip);
+ const char *name = mtd->name;
+
+ if (!controller->release)
+ controller = NULL;
+ nand_cleanup(chip);
+ of_node_put(np);
+ kfree(name);
+ if (chip->release)
+ chip->release(chip);
+ if (controller)
+ nand_controller_put(controller);
+ put_device(parent);
+}
+
+/**
+ * nand_set_release - retain a scanned chip until its final MTD reference
+ * @chip: successfully scanned, unpublished NAND chip
+ * @release: callback freeing chip storage, or NULL for a controller-embedded chip
+ *
+ * Copies the MTD name and pins the parent device, flash node and, when managed
+ * with devm_nand_controller_init(), controller storage. The original name stays
+ * owned by its original allocator. The caller must set the name or parent before
+ * calling this function, and preserve any other metadata used by MTD callbacks.
+ *
+ * After success, unregister MTD, call nand_disconnect(), and drop the driver's
+ * initial reference with mtd_put_master(). Do the same disconnect/put on MTD
+ * registration failure. nand_cleanup() runs at final release, so controller
+ * detach callbacks must not depend on resources released during removal.
+ *
+ * Return: zero on success or -ENOMEM, leaving ownership unchanged on failure.
+ */
+int nand_set_release(struct nand_chip *chip,
+ void (*release)(struct nand_chip *chip))
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ const char *name = mtd->name;
+ char *copy;
+
+ if (!name)
+ name = dev_name(mtd->dev.parent);
+ copy = kstrdup(name, GFP_KERNEL);
+ if (!copy)
+ return -ENOMEM;
+
+ mtd->name = copy;
+ mtd->_get_device = nand_mtd_get_device;
+ get_device(mtd->dev.parent);
+ of_node_get(nand_get_flash_node(chip));
+ if (chip->controller->release)
+ kref_get(&chip->controller->refcnt);
+ chip->release = release;
+ mtd_set_release(mtd, nand_mtd_release);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nand_set_release);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 90b2169cc745..313e0d88e19a 100644
--- a/include/linux/mtd/rawnand.h
+++ b/include/linux/mtd/rawnand.h
@@ -22,6 +22,7 @@
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/types.h>
+#include <linux/kref.h>
struct nand_chip;
struct gpio_desc;
@@ -1116,6 +1117,8 @@ struct nand_controller_ops {
* changing the column.
* @supported_op.cont_read: The controller supports sequential cache reads.
* @controller_wp: the controller is in charge of handling the WP pin.
+ * @refcnt: controller storage references for managed lifetimes
+ * @release: free controller storage after the last chip is released
*/
struct nand_controller {
struct mutex lock;
@@ -1125,6 +1128,8 @@ struct nand_controller {
unsigned int cont_read: 1;
} supported_op;
bool controller_wp;
+ struct kref refcnt;
+ void (*release)(struct nand_controller *controller);
};
static inline void nand_controller_init(struct nand_controller *nfc)
@@ -1132,6 +1137,9 @@ static inline void nand_controller_init(struct nand_controller *nfc)
mutex_init(&nfc->lock);
}
+int devm_nand_controller_init(struct device *dev, struct nand_controller *controller,
+ void (*release)(struct nand_controller *controller));
+
/**
* struct nand_legacy - NAND chip legacy fields/hooks
* @IO_ADDR_R: address to read the 8 I/O lines of the flash device
@@ -1285,6 +1293,7 @@ struct nand_secure_region {
* independent devices
* @ecc: The ECC controller structure
* @priv: Chip private data
+ * @release: Free chip storage after the last MTD reference is dropped
*/
struct nand_chip {
struct nand_device base;
@@ -1345,6 +1354,7 @@ struct nand_chip {
struct nand_controller *controller;
struct nand_ecc_ctrl ecc;
void *priv;
+ void (*release)(struct nand_chip *chip);
};
static inline struct nand_chip *mtd_to_nand(struct mtd_info *mtd)
@@ -1598,6 +1608,8 @@ void nand_wait_ready(struct nand_chip *chip);
*/
void nand_cleanup(struct nand_chip *chip);
void nand_disconnect(struct nand_chip *chip);
+int nand_set_release(struct nand_chip *chip,
+ void (*release)(struct nand_chip *chip));
/*
* External helper for controller drivers that have to implement the WAITRDY
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 06/58] mtd: rawnand: macronix: serialize OTP access with NAND operations
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (4 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 05/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
` (17 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
The Macronix OTP callbacks select a target and issue feature and page
commands without taking the chip or controller locks. A retained OTP
handle can therefore issue commands after controller disconnection, and
normal NAND I/O can also interfere with the temporary OTP mode.
Take the common NAND operation locks around the entire OTP transaction,
including restoring normal mode. This also rejects operations on a
disconnected chip and waits for suspended chips using the normal NAND
rules.
Fixes: e87161321a40 ("mtd: rawnand: macronix: OTP access for MX30LFxG18AC")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/internals.h | 2 ++
drivers/mtd/nand/raw/nand_base.c | 4 ++--
drivers/mtd/nand/raw/nand_macronix.c | 4 ++++
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h
index b8b29341b88c..dcd9536da449 100644
--- a/drivers/mtd/nand/raw/internals.h
+++ b/drivers/mtd/nand/raw/internals.h
@@ -81,6 +81,8 @@ extern const struct nand_manufacturer_ops toshiba_nand_manuf_ops;
extern const struct mtd_pairing_scheme dist3_pairing_scheme;
/* Core functions */
+int nand_get_device(struct nand_chip *chip);
+void nand_release_device(struct nand_chip *chip);
const struct nand_manufacturer_desc *nand_get_manufacturer_desc(u8 id);
int nand_bbm_get_next_page(struct nand_chip *chip, int page);
int nand_markbad_bbm(struct nand_chip *chip, loff_t ofs);
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index 8f7d2571e8d9..2667faf02a91 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -207,7 +207,7 @@ EXPORT_SYMBOL_GPL(nand_deselect_target);
*
* Release chip lock and wake up anyone waiting on the device.
*/
-static void nand_release_device(struct nand_chip *chip)
+void nand_release_device(struct nand_chip *chip)
{
/* Release the controller and the chip */
mutex_unlock(&chip->controller->lock);
@@ -336,7 +336,7 @@ static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs)
*
* Lock the device and its controller for exclusive access
*/
-static int nand_get_device(struct nand_chip *chip)
+int nand_get_device(struct nand_chip *chip)
{
/* Wait until the device is resumed. */
while (1) {
diff --git a/drivers/mtd/nand/raw/nand_macronix.c b/drivers/mtd/nand/raw/nand_macronix.c
index 03237310852c..1ffe2fc4943e 100644
--- a/drivers/mtd/nand/raw/nand_macronix.c
+++ b/drivers/mtd/nand/raw/nand_macronix.c
@@ -380,6 +380,9 @@ static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd,
int ret;
nand = mtd_to_nand(mtd);
+ ret = nand_get_device(nand);
+ if (ret)
+ return ret;
nand_select_target(nand, 0);
ret = macronix_30lfxg18ac_otp_enable(nand);
@@ -426,6 +429,7 @@ static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd,
write ? "write" : "read");
nand_deselect_target(nand);
+ nand_release_device(nand);
return ret;
}
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (5 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 06/58] mtd: rawnand: macronix: serialize OTP access with NAND operations James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
` (16 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
sunxi_nand_chips_cleanup() unregisters each MTD device and immediately
calls nand_cleanup(). Devres subsequently frees the chip and controller.
An open MTD handle can outlive this sequence: another operation or even
closing a writable handle reaches freed metadata and lock storage.
Unregister each MTD device while removal notifiers can still finish I/O,
then disconnect the chip and release the driver's initial MTD lifetime
reference. Defer nand_cleanup() and chip destruction until the final MTD
or device reference is released. Retain the chip name, flash node and
per-sector user-data lengths along with the NAND metadata.
Give the controller a reference for each retained chip and one owned by
devres. Register that devres action first so the IRQ and hardware
resources are released before the initial controller reference. The
shared lock and parent device remain valid until the last chip is
destroyed, while the disconnect gate prevents access to released
hardware resources.
Unwind heap allocations on probe failures, including attach failures for
which nand_scan() does not invoke the detach callback.
Fixes: 1fef62c1423b ("mtd: nand: add sunxi NAND flash controller support")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sunxi_nand.c | 59 +++++++++++++++++++++++++++++----------
1 file changed, 45 insertions(+), 14 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index f41feebe4257..57894ce45065 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -27,6 +27,7 @@
#include <linux/dmaengine.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
+#include <linux/mutex.h>
#include <linux/reset.h>
#define NFC_REG_CTL 0x0000
@@ -405,6 +406,13 @@ static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_controller *ctrl)
return container_of(ctrl, struct sunxi_nfc, controller);
}
+static void sunxi_nfc_release(struct nand_controller *controller)
+{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(controller);
+
+ kfree(nfc);
+}
+
static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id)
{
struct sunxi_nfc *nfc = dev_id;
@@ -2027,9 +2035,8 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
static void sunxi_nand_detach_chip(struct nand_chip *nand)
{
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
- struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
+ kfree(sunxi_nand->user_data_bytes);
sunxi_nand->user_data_bytes = NULL;
}
@@ -2045,8 +2052,7 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
if (nsectors <= 0)
return -EINVAL;
- sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
- GFP_KERNEL);
+ sunxi_nand->user_data_bytes = kzalloc(nsectors, GFP_KERNEL);
if (!sunxi_nand->user_data_bytes)
return -ENOMEM;
@@ -2427,6 +2433,13 @@ static const struct nand_controller_ops sunxi_nand_controller_ops = {
.exec_op = sunxi_nfc_exec_op,
};
+static void sunxi_nand_release(struct nand_chip *chip)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(chip);
+
+ kfree(sunxi_nand);
+}
+
static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc)
{
struct sunxi_nand_chip *sunxi_nand;
@@ -2440,8 +2453,9 @@ static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc)
chip = &sunxi_nand->nand;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&sunxi_nand->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -2465,8 +2479,7 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
return -EINVAL;
}
- sunxi_nand = devm_kzalloc(dev, struct_size(sunxi_nand, sels, nsels),
- GFP_KERNEL);
+ sunxi_nand = kzalloc(struct_size(sunxi_nand, sels, nsels), GFP_KERNEL);
if (!sunxi_nand)
return -ENOMEM;
@@ -2477,19 +2490,21 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
if (ret) {
dev_err(dev, "could not retrieve reg property: %d\n",
ret);
- return ret;
+ goto err_free_chip;
}
if (tmp > NFC_MAX_CS) {
dev_err(dev,
"invalid reg value: %u (max CS = 7)\n",
tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_free_chip;
}
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
dev_err(dev, "CS %d already assigned\n", tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto err_free_chip;
}
sunxi_nand->sels[i].cs = tmp;
@@ -2518,18 +2533,32 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
ret = nand_scan(nand, nsels);
if (ret)
- return ret;
+ goto err_free_chip;
+
+ ret = nand_set_release(nand, sunxi_nand_release);
+ if (ret)
+ goto err_cleanup_nand;
+ mtd->owner = THIS_MODULE;
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "failed to register mtd device: %d\n", ret);
- nand_cleanup(nand);
+ nand_disconnect(nand);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&sunxi_nand->node, &nfc->chips);
return 0;
+
+err_cleanup_nand:
+ nand_cleanup(nand);
+err_free_chip:
+ /* nand_scan() does not detach a chip whose attach callback failed. */
+ kfree(sunxi_nand->user_data_bytes);
+ kfree(sunxi_nand);
+ return ret;
}
static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc)
@@ -2610,12 +2639,14 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
int irq;
int ret;
- nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
nfc->dev = dev;
- nand_controller_init(&nfc->controller);
+ ret = devm_nand_controller_init(dev, &nfc->controller, sunxi_nfc_release);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (6 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 09/58] mtd: rawnand: gpio: " James Hilliard
` (15 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Unregister MTD while notifier clients can finish their I/O, then
disconnect the NAND chip before invoking the platform hardware teardown.
Keep the chip, controller lock and software ECC metadata until the last
MTD reference is dropped.
Handle registration failures through the same reference-counted cleanup,
since partitions can have become visible before registration fails.
Fixes: 711fdf627ce1 ("[MTD] [NAND] platform NAND driver: add driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/plat_nand.c | 24 +++++++++++++++++++-----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/plat_nand.c b/drivers/mtd/nand/raw/plat_nand.c
index fe31551bcf5f..8321667fae2f 100644
--- a/drivers/mtd/nand/raw/plat_nand.c
+++ b/drivers/mtd/nand/raw/plat_nand.c
@@ -21,6 +21,11 @@ struct plat_nand_data {
struct gpio_desc *ready_gpio;
};
+static void plat_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct plat_nand_data, controller));
+}
+
static int plat_nand_attach_chip(struct nand_chip *chip)
{
if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT &&
@@ -65,10 +70,13 @@ static int plat_nand_probe(struct platform_device *pdev)
}
/* Allocate memory for the device structure (and zero it) */
- data = devm_kzalloc(&pdev->dev, sizeof(struct plat_nand_data),
- GFP_KERNEL);
+ data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &data->controller,
+ plat_nand_release);
+ if (err)
+ return err;
data->ready_gpio = devm_gpiod_get_optional(&pdev->dev, "ready",
GPIOD_IN);
@@ -77,7 +85,6 @@ static int plat_nand_probe(struct platform_device *pdev)
"could not get READY GPIO\n");
data->controller.ops = &plat_nand_ops;
- nand_controller_init(&data->controller);
data->chip.controller = &data->controller;
chip = &data->chip;
nand_set_controller_data(chip, data);
@@ -126,6 +133,11 @@ static int plat_nand_probe(struct platform_device *pdev)
goto out;
part_types = pdata->chip.part_probe_types;
+ err = nand_set_release(chip, NULL);
+ if (err) {
+ nand_cleanup(chip);
+ goto out;
+ }
err = mtd_device_parse_register(mtd, part_types, NULL,
pdata->chip.partitions,
@@ -134,7 +146,8 @@ static int plat_nand_probe(struct platform_device *pdev)
if (!err)
return err;
- nand_cleanup(&data->chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
out:
if (pdata->ctrl.remove)
pdata->ctrl.remove(pdev);
@@ -153,9 +166,10 @@ static void plat_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
if (pdata->ctrl.remove)
pdata->ctrl.remove(pdev);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id plat_nand_match[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 09/58] mtd: rawnand: gpio: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (7 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
` (14 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
A retained MTD handle can outlive GPIO NAND unbind. Drain and disconnect
NAND operations before disabling the chip, and keep the chip metadata
and controller lock alive until the final MTD reference is released.
GPIO and MMIO resources are still released during unbind.
Also release scanned NAND metadata when MTD registration fails, using
retained ownership once a device could have been published.
Fixes: aaf7ea200004 ("[MTD] [NAND] GPIO NAND flash driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/gpio.c | 21 ++++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/gpio.c b/drivers/mtd/nand/raw/gpio.c
index 69e5e43532a4..b6c98c374e39 100644
--- a/drivers/mtd/nand/raw/gpio.c
+++ b/drivers/mtd/nand/raw/gpio.c
@@ -265,6 +265,11 @@ gpio_nand_get_io_sync(struct platform_device *pdev)
return platform_get_resource(pdev, IORESOURCE_MEM, 1);
}
+static void gpio_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct gpiomtd, base));
+}
+
static void gpio_nand_remove(struct platform_device *pdev)
{
struct gpiomtd *gpiomtd = platform_get_drvdata(pdev);
@@ -273,13 +278,14 @@ static void gpio_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
/* Enable write protection and disable the chip */
if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp))
gpiod_set_value(gpiomtd->nwp, 0);
if (gpiomtd->nce && !IS_ERR(gpiomtd->nce))
gpiod_set_value(gpiomtd->nce, 0);
+ mtd_put_master(nand_to_mtd(chip));
}
static int gpio_nand_probe(struct platform_device *pdev)
@@ -294,9 +300,12 @@ static int gpio_nand_probe(struct platform_device *pdev)
if (!dev->of_node && !dev_get_platdata(dev))
return -EINVAL;
- gpiomtd = devm_kzalloc(dev, sizeof(*gpiomtd), GFP_KERNEL);
+ gpiomtd = kzalloc_obj(*gpiomtd);
if (!gpiomtd)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &gpiomtd->base, gpio_nand_release);
+ if (ret)
+ return ret;
chip = &gpiomtd->nand_chip;
@@ -345,7 +354,6 @@ static int gpio_nand_probe(struct platform_device *pdev)
goto out_ce;
}
- nand_controller_init(&gpiomtd->base);
gpiomtd->base.ops = &gpio_nand_ops;
nand_set_flash_node(chip, pdev->dev.of_node);
@@ -374,11 +382,18 @@ static int gpio_nand_probe(struct platform_device *pdev)
if (gpiomtd->plat.adjust_parts)
gpiomtd->plat.adjust_parts(&gpiomtd->plat, mtd->size);
+ ret = nand_set_release(chip, NULL);
+ if (ret) {
+ nand_cleanup(chip);
+ goto err_wp;
+ }
ret = mtd_device_register(mtd, gpiomtd->plat.parts,
gpiomtd->plat.num_parts);
if (!ret)
return 0;
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
err_wp:
if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp))
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 10/58] mtd: rawnand: stm32_fmc2: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (8 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 09/58] mtd: rawnand: gpio: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
` (13 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep the embedded NAND chip and controller serialization state alive for
MTD references that survive unbind. Disconnect operations before
releasing DMA channels and scatterlists and asserting write protection;
defer NAND metadata cleanup and controller storage release until the
final reference.
Preserve immediate cleanup before publication and use the retained
lifetime after MTD registration has been attempted.
Fixes: 2cd457f328c1 ("mtd: rawnand: stm32_fmc2: add STM32 FMC2 NAND flash controller driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/stm32_fmc2_nand.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index 9f42f8ab3212..4b89094c89e6 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -1902,6 +1902,11 @@ static int stm32_fmc2_nfc_set_cdev(struct stm32_fmc2_nfc *nfc)
return 0;
}
+static void stm32_fmc2_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct stm32_fmc2_nfc, base));
+}
+
static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1915,12 +1920,14 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
int chip_cs, mem_region, ret, irq;
int start_region = 0;
- nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nfc->base, stm32_fmc2_nfc_release);
+ if (ret)
+ return ret;
nfc->dev = dev;
- nand_controller_init(&nfc->base);
nfc->base.ops = &stm32_fmc2_nfc_controller_ops;
nfc->data = of_device_get_match_data(dev);
@@ -2024,9 +2031,15 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
if (ret)
goto err_wp_enable;
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(chip, NULL);
if (ret)
goto err_nand_cleanup;
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ goto err_wp_enable;
+ }
platform_set_drvdata(pdev, nfc);
@@ -2061,7 +2074,7 @@ static void stm32_fmc2_nfc_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
if (nfc->dma_ecc_ch)
dma_release_channel(nfc->dma_ecc_ch);
@@ -2074,6 +2087,7 @@ static void stm32_fmc2_nfc_remove(struct platform_device *pdev)
sg_free_table(&nfc->dma_ecc_sg);
stm32_fmc2_nfc_wp_enable(nand);
+ mtd_put_master(nand_to_mtd(chip));
}
static int stm32_fmc2_nfc_suspend(struct device *dev)
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 11/58] mtd: rawnand: denali: retain chips across controller removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (9 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 12/58] mtd: rawnand: arasan: " James Hilliard
` (12 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Both Denali frontends allocate chip and controller objects with
device-managed storage that disappears on unbind even when MTD
references remain. Give each chip a retained lifetime and a reference to
the controller, and keep the initial controller reference until IRQ and
mapped hardware resources have been released.
Unregister and disconnect each chip before disabling the controller.
Remove its list entry before dropping ownership, and use the same
release path for partially published probe failures. Denali chip
initialization now consumes its chip allocation on both success and
failure.
Fixes: ce082596ae43 ("mtd/nand: Add Intel Moorestown/Denali NAND support")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/denali.c | 31 +++++++++++++++++++++++--------
drivers/mtd/nand/raw/denali_dt.c | 21 ++++++++++++++++-----
drivers/mtd/nand/raw/denali_pci.c | 15 ++++++++++++---
3 files changed, 51 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c
index fa2439cb4daa..4525cec3cae8 100644
--- a/drivers/mtd/nand/raw/denali.c
+++ b/drivers/mtd/nand/raw/denali.c
@@ -1176,6 +1176,12 @@ static const struct nand_controller_ops denali_controller_ops = {
.setup_interface = denali_setup_interface,
};
+static void denali_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct denali_chip, chip));
+}
+
+/* Takes ownership of dchip, including on failure. */
int denali_chip_init(struct denali_controller *denali,
struct denali_chip *dchip)
{
@@ -1192,7 +1198,8 @@ int denali_chip_init(struct denali_controller *denali,
if (bank >= denali->nbanks) {
dev_err(denali->dev, "unsupported bank %d\n", bank);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
for (j = 0; j < i; j++) {
@@ -1200,7 +1207,8 @@ int denali_chip_init(struct denali_controller *denali,
dev_err(denali->dev,
"bank %d is assigned twice in the same chip\n",
bank);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
}
@@ -1210,7 +1218,8 @@ int denali_chip_init(struct denali_controller *denali,
dev_err(denali->dev,
"bank %d is already used\n",
bank);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
}
}
@@ -1250,12 +1259,17 @@ int denali_chip_init(struct denali_controller *denali,
ret = nand_scan(chip, dchip->nsels);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_release(chip, denali_chip_release);
+ if (ret)
+ goto cleanup_nand;
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(denali->dev, "Failed to register MTD: %d\n", ret);
- goto cleanup_nand;
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ return ret;
}
list_add_tail(&dchip->node, &denali->chips);
@@ -1264,7 +1278,8 @@ int denali_chip_init(struct denali_controller *denali,
cleanup_nand:
nand_cleanup(chip);
-
+free_chip:
+ kfree(dchip);
return ret;
}
EXPORT_SYMBOL_GPL(denali_chip_init);
@@ -1274,7 +1289,6 @@ int denali_init(struct denali_controller *denali)
u32 features = ioread32(denali->reg + FEATURES);
int ret;
- nand_controller_init(&denali->controller);
denali->controller.ops = &denali_controller_ops;
init_completion(&denali->complete);
spin_lock_init(&denali->irq_lock);
@@ -1368,8 +1382,9 @@ void denali_remove(struct denali_controller *denali)
chip = &dchip->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&dchip->node);
+ mtd_put_master(nand_to_mtd(chip));
}
denali_disable_irq(denali);
diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c
index 8c822eae72e7..8d813cc05d04 100644
--- a/drivers/mtd/nand/raw/denali_dt.c
+++ b/drivers/mtd/nand/raw/denali_dt.c
@@ -15,6 +15,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
+#include <linux/slab.h>
#include "denali.h"
@@ -81,7 +82,7 @@ MODULE_DEVICE_TABLE(of, denali_nand_dt_ids);
static int denali_dt_chip_init(struct denali_controller *denali,
struct device_node *chip_np)
{
- struct denali_chip *dchip;
+ struct denali_chip *dchip __free(kfree) = NULL;
u32 bank;
int nsels, i, ret;
@@ -89,8 +90,7 @@ static int denali_dt_chip_init(struct denali_controller *denali,
if (nsels < 0)
return nsels;
- dchip = devm_kzalloc(denali->dev, struct_size(dchip, sels, nsels),
- GFP_KERNEL);
+ dchip = kzalloc(struct_size(dchip, sels, nsels), GFP_KERNEL);
if (!dchip)
return -ENOMEM;
@@ -106,7 +106,15 @@ static int denali_dt_chip_init(struct denali_controller *denali,
nand_set_flash_node(&dchip->chip, chip_np);
}
- return denali_chip_init(denali, dchip);
+ return denali_chip_init(denali, no_free_ptr(dchip));
+}
+
+static void denali_dt_release(struct nand_controller *controller)
+{
+ struct denali_controller *denali;
+
+ denali = container_of(controller, struct denali_controller, controller);
+ kfree(container_of(denali, struct denali_dt, controller));
}
static int denali_dt_probe(struct platform_device *pdev)
@@ -117,10 +125,13 @@ static int denali_dt_probe(struct platform_device *pdev)
struct denali_controller *denali;
int ret;
- dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL);
+ dt = kzalloc_obj(*dt);
if (!dt)
return -ENOMEM;
denali = &dt->controller;
+ ret = devm_nand_controller_init(dev, &denali->controller, denali_dt_release);
+ if (ret)
+ return ret;
data = of_device_get_match_data(dev);
if (WARN_ON(!data))
diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c
index f53df1b32dda..c39abe71941f 100644
--- a/drivers/mtd/nand/raw/denali_pci.c
+++ b/drivers/mtd/nand/raw/denali_pci.c
@@ -9,6 +9,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/slab.h>
#include "denali.h"
@@ -27,6 +28,11 @@ MODULE_DEVICE_TABLE(pci, denali_pci_ids);
NAND_ECC_CAPS_SINGLE(denali_pci_ecc_caps, denali_calc_ecc_bytes, 512, 8, 15);
+static void denali_pci_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct denali_controller, controller));
+}
+
static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
resource_size_t csr_base, mem_base;
@@ -35,9 +41,13 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
struct denali_chip *dchip;
int nsels, ret, i;
- denali = devm_kzalloc(&dev->dev, sizeof(*denali), GFP_KERNEL);
+ denali = kzalloc_obj(*denali);
if (!denali)
return -ENOMEM;
+ ret = devm_nand_controller_init(&dev->dev, &denali->controller,
+ denali_pci_release);
+ if (ret)
+ return ret;
ret = pcim_enable_device(dev);
if (ret) {
@@ -92,8 +102,7 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
nsels = denali->nbanks;
- dchip = devm_kzalloc(denali->dev, struct_size(dchip, sels, nsels),
- GFP_KERNEL);
+ dchip = kzalloc(struct_size(dchip, sels, nsels), GFP_KERNEL);
if (!dchip) {
ret = -ENOMEM;
goto out_remove_denali;
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 12/58] mtd: rawnand: arasan: retain chips across controller removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (10 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
` (11 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata, BCH state and controller storage until the last MTD
reference is released. Unregister and disconnect the chips before
releasing chip-select GPIOs, so notifier I/O and active NAND operations
finish while the hardware is usable.
Free unpublished chip allocations directly and use retained cleanup
after MTD registration has been attempted.
Fixes: 197b88fecc50 ("mtd: rawnand: arasan: Add new Arasan NAND controller")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/arasan-nand-controller.c | 57 ++++++++++++++++++++-------
1 file changed, 42 insertions(+), 15 deletions(-)
diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c
index 865754737f5f..57b5d6119b70 100644
--- a/drivers/mtd/nand/raw/arasan-nand-controller.c
+++ b/drivers/mtd/nand/raw/arasan-nand-controller.c
@@ -1269,6 +1269,11 @@ static const struct nand_controller_ops anfc_ops = {
.detach_chip = anfc_detach_chip,
};
+static void anfc_chip_release(struct nand_chip *chip)
+{
+ kfree(to_anand(chip));
+}
+
static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np)
{
struct anand *anand;
@@ -1276,7 +1281,7 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np)
struct mtd_info *mtd;
int rb, ret, i;
- anand = devm_kzalloc(nfc->dev, sizeof(*anand), GFP_KERNEL);
+ anand = kzalloc_obj(*anand);
if (!anand)
return -ENOMEM;
@@ -1284,31 +1289,35 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np)
anand->ncs_idx = of_property_count_elems_of_size(np, "reg", sizeof(u32));
if (anand->ncs_idx <= 0 || anand->ncs_idx > nfc->ncs) {
dev_err(nfc->dev, "Invalid reg property\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
anand->cs_idx = devm_kcalloc(nfc->dev, anand->ncs_idx,
sizeof(*anand->cs_idx), GFP_KERNEL);
- if (!anand->cs_idx)
- return -ENOMEM;
+ if (!anand->cs_idx) {
+ ret = -ENOMEM;
+ goto free_chip;
+ }
for (i = 0; i < anand->ncs_idx; i++) {
ret = of_property_read_u32_index(np, "reg", i,
&anand->cs_idx[i]);
if (ret) {
dev_err(nfc->dev, "invalid CS property: %d\n", ret);
- return ret;
+ goto free_chip;
}
}
/* Ready-busy init */
ret = of_property_read_u32(np, "nand-rb", &rb);
if (ret)
- return ret;
+ goto free_chip;
if (rb >= ANFC_MAX_CS) {
dev_err(nfc->dev, "Wrong RB %d\n", rb);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
anand->rb = rb;
@@ -1323,24 +1332,35 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np)
nand_set_flash_node(chip, np);
if (!mtd->name) {
dev_err(nfc->dev, "NAND label property is mandatory\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
ret = nand_scan(chip, anand->ncs_idx);
if (ret) {
dev_err(nfc->dev, "Scan operation failed\n");
- return ret;
+ goto free_chip;
}
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(chip, anfc_chip_release);
if (ret) {
nand_cleanup(chip);
+ goto free_chip;
+ }
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&anand->node, &nfc->chips);
return 0;
+
+free_chip:
+ kfree(anand);
+ return ret;
}
static void anfc_chips_cleanup(struct arasan_nfc *nfc)
@@ -1353,8 +1373,9 @@ static void anfc_chips_cleanup(struct arasan_nfc *nfc)
chip = &anand->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&anand->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -1431,17 +1452,24 @@ static int anfc_parse_cs(struct arasan_nfc *nfc)
return 0;
}
+static void anfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct arasan_nfc, controller));
+}
+
static int anfc_probe(struct platform_device *pdev)
{
struct arasan_nfc *nfc;
int ret;
- nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &nfc->controller, anfc_release);
+ if (ret)
+ return ret;
nfc->dev = &pdev->dev;
- nand_controller_init(&nfc->controller);
nfc->controller.ops = &anfc_ops;
INIT_LIST_HEAD(&nfc->chips);
@@ -1481,13 +1509,12 @@ static void anfc_remove(struct platform_device *pdev)
int i;
struct arasan_nfc *nfc = platform_get_drvdata(pdev);
+ anfc_chips_cleanup(nfc);
for (i = 0; i < nfc->ncs; i++) {
if (nfc->cs_array[i]) {
gpiod_put(nfc->cs_array[i]);
}
}
-
- anfc_chips_cleanup(nfc);
}
static const struct of_device_id anfc_ids[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 13/58] mtd: rawnand: mxic: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (11 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 12/58] mtd: rawnand: arasan: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 14/58] mtd: rawnand: intel: " James Hilliard
` (10 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
A retained MTD handle must not access controller storage freed by
unbind. Disconnect NAND operations before disabling clocks and defer
NAND metadata and controller storage cleanup until the final MTD
reference.
Release scanned NAND metadata on registration failure as well, using
retained ownership once publication has been attempted.
Fixes: 738b0ca55f4f ("mtd: rawnand: Add Macronix raw NAND controller driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/mxic_nand.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/mxic_nand.c b/drivers/mtd/nand/raw/mxic_nand.c
index 92de26697359..0d20e6ba76a2 100644
--- a/drivers/mtd/nand/raw/mxic_nand.c
+++ b/drivers/mtd/nand/raw/mxic_nand.c
@@ -15,6 +15,7 @@
#include <linux/mtd/nand-ecc-sw-hamming.h>
#include <linux/mtd/rawnand.h>
#include <linux/platform_device.h>
+#include <linux/slab.h>
#include "internals.h"
@@ -483,6 +484,11 @@ static const struct nand_controller_ops mxic_nand_controller_ops = {
.setup_interface = mxic_nfc_setup_interface,
};
+static void mxic_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct mxic_nand_ctlr, controller));
+}
+
static int mxic_nfc_probe(struct platform_device *pdev)
{
struct device_node *nand_np, *np = pdev->dev.of_node;
@@ -492,10 +498,12 @@ static int mxic_nfc_probe(struct platform_device *pdev)
int err;
int irq;
- nfc = devm_kzalloc(&pdev->dev, sizeof(struct mxic_nand_ctlr),
- GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &nfc->controller, mxic_nfc_release);
+ if (err)
+ return err;
nfc->ps_clk = devm_clk_get(&pdev->dev, "ps");
if (IS_ERR(nfc->ps_clk))
@@ -523,7 +531,6 @@ static int mxic_nfc_probe(struct platform_device *pdev)
nand_chip->priv = nfc;
nfc->dev = &pdev->dev;
nfc->controller.ops = &mxic_nand_controller_ops;
- nand_controller_init(&nfc->controller);
nand_chip->controller = &nfc->controller;
irq = platform_get_irq(pdev, 0);
@@ -541,9 +548,17 @@ static int mxic_nfc_probe(struct platform_device *pdev)
if (err)
goto fail;
+ err = nand_set_release(nand_chip, NULL);
+ if (err) {
+ nand_cleanup(nand_chip);
+ goto fail;
+ }
err = mtd_device_register(mtd, NULL, 0);
- if (err)
+ if (err) {
+ nand_disconnect(nand_chip);
+ mtd_put_master(mtd);
goto fail;
+ }
platform_set_drvdata(pdev, nfc);
return 0;
@@ -561,9 +576,10 @@ static void mxic_nfc_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
mxic_nfc_clk_disable(nfc);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id mxic_nfc_of_ids[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 14/58] mtd: rawnand: intel: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (12 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 15/58] mtd: rawnand: tegra: " James Hilliard
` (9 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Disconnect NAND operations after unregistering MTD and before disabling
the controller or releasing DMA channels. Retain the embedded chip,
controller lock and NAND metadata until the last MTD reference is
released, including references obtained during a partially successful
probe.
Fixes: 0b1039f016e8 ("mtd: rawnand: Add NAND controller support on Intel LGM SoC")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/intel-nand-controller.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c
index 477a427dac1f..e4a24e69ebbc 100644
--- a/drivers/mtd/nand/raw/intel-nand-controller.c
+++ b/drivers/mtd/nand/raw/intel-nand-controller.c
@@ -573,6 +573,11 @@ static void ebu_dma_cleanup(struct ebu_nand_controller *ebu_host)
dma_release_channel(ebu_host->dma_tx);
}
+static void ebu_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct ebu_nand_controller, controller));
+}
+
static int ebu_nand_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -585,12 +590,14 @@ static int ebu_nand_probe(struct platform_device *pdev)
int ret;
u32 cs;
- ebu_host = devm_kzalloc(dev, sizeof(*ebu_host), GFP_KERNEL);
+ ebu_host = kzalloc_obj(*ebu_host);
if (!ebu_host)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &ebu_host->controller, ebu_nand_release);
+ if (ret)
+ return ret;
ebu_host->dev = dev;
- nand_controller_init(&ebu_host->controller);
ebu_host->ebu = devm_platform_ioremap_resource_byname(pdev, "ebunand");
if (IS_ERR(ebu_host->ebu))
@@ -692,9 +699,15 @@ static int ebu_nand_probe(struct platform_device *pdev)
if (ret)
goto err_cleanup_dma;
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(nand, NULL);
if (ret)
goto err_clean_nand;
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(nand);
+ mtd_put_master(mtd);
+ goto err_cleanup_dma;
+ }
return 0;
@@ -715,9 +728,10 @@ static void ebu_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(&ebu_host->chip));
WARN_ON(ret);
- nand_cleanup(&ebu_host->chip);
+ nand_disconnect(&ebu_host->chip);
ebu_nand_disable(&ebu_host->chip);
ebu_dma_cleanup(ebu_host);
+ mtd_put_master(nand_to_mtd(&ebu_host->chip));
}
static const struct of_device_id ebu_nand_match[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 15/58] mtd: rawnand: tegra: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (13 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 14/58] mtd: rawnand: intel: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
` (8 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Retain the chip and controller allocations until their final MTD
references are released. Disconnect the chip before suspending the
controller so retained handles cannot reach powered-down hardware. Leave
metadata cleanup to the final put, including registration-failure
unwinding.
Fixes: d7d9f8ec77fe ("mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/tegra_nand.c | 37 ++++++++++++++++++++++++++++++-------
1 file changed, 30 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
index 7f9eb5f042a7..da7791741b9d 100644
--- a/drivers/mtd/nand/raw/tegra_nand.c
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -1064,6 +1064,11 @@ static const struct nand_controller_ops tegra_nand_controller_ops = {
.setup_interface = tegra_nand_setup_interface,
};
+static void tegra_nand_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct tegra_nand_chip, chip));
+}
+
static int tegra_nand_chips_init(struct device *dev,
struct tegra_nand_controller *ctrl)
{
@@ -1096,7 +1101,7 @@ static int tegra_nand_chips_init(struct device *dev,
return ret;
}
- nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
+ nand = kzalloc_obj(*nand);
if (!nand)
return -ENOMEM;
@@ -1107,7 +1112,7 @@ static int tegra_nand_chips_init(struct device *dev,
if (IS_ERR(nand->wp_gpio)) {
ret = PTR_ERR(nand->wp_gpio);
dev_err(dev, "Failed to request WP GPIO: %d\n", ret);
- return ret;
+ goto free_chip;
}
chip = &nand->chip;
@@ -1127,20 +1132,35 @@ static int tegra_nand_chips_init(struct device *dev,
ret = nand_scan(chip, 1);
if (ret)
- return ret;
+ goto free_chip;
mtd_ooblayout_ecc(mtd, 0, &nand->ecc);
+ ret = nand_set_release(chip, tegra_nand_chip_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "Failed to register mtd device: %d\n", ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
ctrl->chip = chip;
return 0;
+
+free_chip:
+ kfree(nand);
+ return ret;
+}
+
+static void tegra_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct tegra_nand_controller, controller));
}
static int tegra_nand_probe(struct platform_device *pdev)
@@ -1149,13 +1169,15 @@ static int tegra_nand_probe(struct platform_device *pdev)
struct tegra_nand_controller *ctrl;
int err = 0;
- ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL);
+ ctrl = kzalloc_obj(*ctrl);
if (!ctrl)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &ctrl->controller, tegra_nand_release);
+ if (err)
+ return err;
ctrl->dev = &pdev->dev;
platform_set_drvdata(pdev, ctrl);
- nand_controller_init(&ctrl->controller);
ctrl->controller.ops = &tegra_nand_controller_ops;
ctrl->regs = devm_platform_ioremap_resource(pdev, 0);
@@ -1232,10 +1254,11 @@ static void tegra_nand_remove(struct platform_device *pdev)
WARN_ON(mtd_device_unregister(mtd));
- nand_cleanup(chip);
+ nand_disconnect(chip);
pm_runtime_put_sync_suspend(ctrl->dev);
pm_runtime_force_suspend(ctrl->dev);
+ mtd_put_master(mtd);
}
static int __maybe_unused tegra_nand_runtime_resume(struct device *dev)
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 16/58] mtd: rawnand: mtk: retain chips across controller removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (14 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 15/58] mtd: rawnand: tegra: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 17/58] mtd: rawnand: rockchip: " James Hilliard
` (7 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Give scanned chips their own storage lifetime and retain the shared
controller until all chip references have been dropped. Unregister and
disconnect each chip before releasing the ECC provider and clocks.
Unlink chips before the final put and unwind unpublished allocations
directly.
Fixes: 1d6b1e464950 ("mtd: mediatek: driver for MTK Smart Device")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/mtk_nand.c | 44 +++++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c
index 21c7e1102746..bdc3d1519631 100644
--- a/drivers/mtd/nand/raw/mtk_nand.c
+++ b/drivers/mtd/nand/raw/mtk_nand.c
@@ -1336,6 +1336,11 @@ static const struct nand_controller_ops mtk_nfc_controller_ops = {
.exec_op = mtk_nfc_exec_op,
};
+static void mtk_nfc_nand_release(struct nand_chip *nand)
+{
+ kfree(container_of(nand, struct mtk_nfc_nand_chip, nand));
+}
+
static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
struct device_node *np)
{
@@ -1356,8 +1361,7 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
return -EINVAL;
}
- chip = devm_kzalloc(dev, struct_size(chip, sels, nsels),
- GFP_KERNEL);
+ chip = kzalloc(struct_size(chip, sels, nsels), GFP_KERNEL);
if (!chip)
return -ENOMEM;
@@ -1366,17 +1370,19 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
ret = of_property_read_u32_index(np, "reg", i, &tmp);
if (ret) {
dev_err(dev, "reg property failure : %d\n", ret);
- return ret;
+ goto free_chip;
}
if (tmp >= MTK_NAND_MAX_NSELS) {
dev_err(dev, "invalid CS: %u\n", tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
dev_err(dev, "CS %u already assigned\n", tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
chip->sels[i] = tmp;
@@ -1415,18 +1421,28 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
ret = nand_scan(nand, nsels);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_release(nand, mtk_nfc_nand_release);
+ if (ret) {
+ nand_cleanup(nand);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "mtd parse partition error\n");
- nand_cleanup(nand);
+ nand_disconnect(nand);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&chip->node, &nfc->chips);
return 0;
+
+free_chip:
+ kfree(chip);
+ return ret;
}
static void mtk_nfc_nand_chips_cleanup(struct mtk_nfc *nfc)
@@ -1441,8 +1457,9 @@ static void mtk_nfc_nand_chips_cleanup(struct mtk_nfc *nfc)
chip = &mtk_chip->nand;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&mtk_chip->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -1504,6 +1521,11 @@ static const struct of_device_id mtk_nfc_id_table[] = {
};
MODULE_DEVICE_TABLE(of, mtk_nfc_id_table);
+static void mtk_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct mtk_nfc, controller));
+}
+
static int mtk_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1511,11 +1533,13 @@ static int mtk_nfc_probe(struct platform_device *pdev)
struct mtk_nfc *nfc;
int ret, irq;
- nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
- nand_controller_init(&nfc->controller);
+ ret = devm_nand_controller_init(dev, &nfc->controller, mtk_nfc_release);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->controller.ops = &mtk_nfc_controller_ops;
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 17/58] mtd: rawnand: rockchip: retain chips across controller removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (15 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 18/58] mtd: rawnand: atmel: " James Hilliard
` (6 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive for retained MTD
handles. Disconnect each chip after unregistering it and before
releasing transfer buffers or clocks. This also lets unregister
notifiers finish using those buffers before hardware teardown. Free chip
metadata on the final MTD put.
Fixes: 058e0e847d54 ("mtd: rawnand: rockchip: NFC driver for RK3308, RK2928 and others")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/rockchip-nand-controller.c | 49 +++++++++++++++++++------
1 file changed, 37 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index 9444ba02696d..aa31457390cd 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -1115,6 +1115,11 @@ static const struct nand_controller_ops rk_nfc_controller_ops = {
.setup_interface = rk_nfc_setup_interface,
};
+static void rk_nfc_nand_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct rk_nfc_nand_chip, chip));
+}
+
static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
struct device_node *np)
{
@@ -1134,8 +1139,7 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
return -EINVAL;
}
- rknand = devm_kzalloc(dev, struct_size(rknand, sels, nsels),
- GFP_KERNEL);
+ rknand = kzalloc(struct_size(rknand, sels, nsels), GFP_KERNEL);
if (!rknand)
return -ENOMEM;
@@ -1144,17 +1148,19 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
ret = of_property_read_u32_index(np, "reg", i, &tmp);
if (ret) {
dev_err(dev, "reg property failure : %d\n", ret);
- return ret;
+ goto free_chip;
}
if (tmp >= NFC_MAX_NSELS) {
dev_err(dev, "invalid CS: %u\n", tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(tmp, &nfc->assigned_cs)) {
dev_err(dev, "CS %u already assigned\n", tmp);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
rknand->sels[i] = tmp;
@@ -1179,14 +1185,15 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
if (!mtd->name) {
dev_err(nfc->dev, "NAND label property is mandatory\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
mtd_set_ooblayout(mtd, &rk_nfc_ooblayout_ops);
rk_nfc_hw_init(nfc);
ret = nand_scan(chip, nsels);
if (ret)
- return ret;
+ goto free_chip;
if (chip->options & NAND_IS_BOOT_MEDIUM) {
ret = of_property_read_u32(np, "rockchip,boot-blks", &tmp);
@@ -1197,16 +1204,26 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
rknand->boot_ecc = ret ? chip->ecc.strength : tmp;
}
+ ret = nand_set_release(chip, rk_nfc_nand_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "MTD parse partition error\n");
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&rknand->node, &nfc->chips);
return 0;
+
+free_chip:
+ kfree(rknand);
+ return ret;
}
static void rk_nfc_chips_cleanup(struct rk_nfc *nfc)
@@ -1219,8 +1236,9 @@ static void rk_nfc_chips_cleanup(struct rk_nfc *nfc)
chip = &rknand->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&rknand->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -1375,17 +1393,24 @@ static const struct of_device_id rk_nfc_id_table[] = {
};
MODULE_DEVICE_TABLE(of, rk_nfc_id_table);
+static void rk_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct rk_nfc, controller));
+}
+
static int rk_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rk_nfc *nfc;
int ret, irq;
- nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
- nand_controller_init(&nfc->controller);
+ ret = devm_nand_controller_init(dev, &nfc->controller, rk_nfc_release);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->controller.ops = &rk_nfc_controller_ops;
@@ -1449,9 +1474,9 @@ static void rk_nfc_remove(struct platform_device *pdev)
{
struct rk_nfc *nfc = platform_get_drvdata(pdev);
+ rk_nfc_chips_cleanup(nfc);
kfree(nfc->page_buf);
kfree(nfc->oob_buf);
- rk_nfc_chips_cleanup(nfc);
rk_nfc_disable_clks(nfc);
}
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 18/58] mtd: rawnand: atmel: retain chips across controller removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (16 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 17/58] mtd: rawnand: rockchip: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
` (5 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep NAND chip metadata and SMC or HSMC controller storage until
retained MTD references have been released. Disconnect registered chips
before releasing DMA, SRAM and clock resources. Give chip initialization
ownership of its allocation on both success and failure, including
absent SmartMedia cards.
Fixes: f88fc122cc34 ("mtd: nand: Cleanup/rework the atmel_nand driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/atmel/nand-controller.c | 59 ++++++++++++++++++++++------
1 file changed, 47 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index 0bdbcbb50d94..10b725fa61d8 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -62,6 +62,7 @@
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <linux/slab.h>
#include <soc/at91/atmel-sfr.h>
#include "pmecc.h"
@@ -1628,8 +1629,9 @@ static int atmel_nand_controller_remove_nand(struct atmel_nand *nand)
if (ret)
return ret;
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&nand->node);
+ mtd_put_master(mtd);
return 0;
}
@@ -1638,7 +1640,7 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
struct device_node *np,
int reg_cells)
{
- struct atmel_nand *nand;
+ struct atmel_nand *nand __free(kfree) = NULL;
struct gpio_desc *gpio;
int numcs, ret, i;
@@ -1649,7 +1651,7 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
return ERR_PTR(-EINVAL);
}
- nand = devm_kzalloc(nc->dev, struct_size(nand, cs, numcs), GFP_KERNEL);
+ nand = kzalloc(struct_size(nand, cs, numcs), GFP_KERNEL);
if (!nand)
return ERR_PTR(-ENOMEM);
@@ -1734,7 +1736,12 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
nand_set_flash_node(&nand->base, np);
- return nand;
+ return_ptr(nand);
+}
+
+static void atmel_nand_release(struct nand_chip *chip)
+{
+ kfree(to_atmel_nand(chip));
}
static int
@@ -1748,6 +1755,7 @@ atmel_nand_controller_add_nand(struct atmel_nand_controller *nc,
/* No card inserted, skip this NAND. */
if (nand->cdgpio && gpiod_get_value(nand->cdgpio)) {
dev_info(nc->dev, "No SmartMedia card inserted.\n");
+ kfree(nand);
return 0;
}
@@ -1756,19 +1764,29 @@ atmel_nand_controller_add_nand(struct atmel_nand_controller *nc,
ret = nand_scan(chip, nand->numcs);
if (ret) {
dev_err(nc->dev, "NAND scan failed: %d\n", ret);
- return ret;
+ goto free_chip;
}
+ ret = nand_set_release(chip, atmel_nand_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(nc->dev, "Failed to register mtd device: %d\n", ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&nand->node, &nc->chips);
return 0;
+
+free_chip:
+ kfree(nand);
+ return ret;
}
static int
@@ -1791,7 +1809,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
{
struct device *dev = nc->dev;
struct platform_device *pdev = to_platform_device(dev);
- struct atmel_nand *nand;
+ struct atmel_nand *nand __free(kfree) = NULL;
struct gpio_desc *gpio;
struct resource *res;
@@ -1799,7 +1817,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
* Legacy bindings only allow connecting a single NAND with a unique CS
* line to the controller.
*/
- nand = devm_kzalloc(nc->dev, struct_size(nand, cs, 1), GFP_KERNEL);
+ nand = kzalloc(struct_size(nand, cs, 1), GFP_KERNEL);
if (!nand)
return -ENOMEM;
@@ -1857,7 +1875,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
nand_set_flash_node(&nand->base, nc->dev->of_node);
- return atmel_nand_controller_add_nand(nc, nand);
+ return atmel_nand_controller_add_nand(nc, no_free_ptr(nand));
}
static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
@@ -2043,7 +2061,6 @@ static int atmel_nand_controller_init(struct atmel_nand_controller *nc,
struct device_node *np = dev->of_node;
int ret;
- nand_controller_init(&nc->base);
nc->base.ops = &atmel_nand_controller_ops;
INIT_LIST_HEAD(&nc->chips);
nc->dev = dev;
@@ -2349,6 +2366,11 @@ atmel_hsmc_nand_controller_remove(struct atmel_nand_controller *nc)
return 0;
}
+static void atmel_hsmc_nand_controller_release(struct nand_controller *controller)
+{
+ kfree(to_hsmc_nand_controller(controller));
+}
+
static int atmel_hsmc_nand_controller_probe(struct platform_device *pdev,
const struct atmel_nand_controller_caps *caps)
{
@@ -2356,9 +2378,13 @@ static int atmel_hsmc_nand_controller_probe(struct platform_device *pdev,
struct atmel_hsmc_nand_controller *nc;
int ret;
- nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL);
+ nc = kzalloc_obj(*nc);
if (!nc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nc->base.base,
+ atmel_hsmc_nand_controller_release);
+ if (ret)
+ return ret;
ret = atmel_nand_controller_init(&nc->base, pdev, caps);
if (ret)
@@ -2426,6 +2452,11 @@ static const struct atmel_nand_controller_caps atmel_sama5_nand_caps = {
.legacy_of_bindings = true,
};
+static void atmel_smc_nand_controller_release(struct nand_controller *controller)
+{
+ kfree(to_smc_nand_controller(controller));
+}
+
static int atmel_smc_nand_controller_probe(struct platform_device *pdev,
const struct atmel_nand_controller_caps *caps)
{
@@ -2433,9 +2464,13 @@ static int atmel_smc_nand_controller_probe(struct platform_device *pdev,
struct atmel_smc_nand_controller *nc;
int ret;
- nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL);
+ nc = kzalloc_obj(*nc);
if (!nc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nc->base.base,
+ atmel_smc_nand_controller_release);
+ if (ret)
+ return ret;
ret = atmel_nand_controller_init(&nc->base, pdev, caps);
if (ret)
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 19/58] mtd: rawnand: orion: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (17 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 18/58] mtd: rawnand: atmel: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 20/58] mtd: rawnand: socrates: " James Hilliard
` (4 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Retain the embedded chip and its copied name through the shared
allocation.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/orion_nand.c | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/orion_nand.c b/drivers/mtd/nand/raw/orion_nand.c
index 47e80d5e58c5..aae054e47db5 100644
--- a/drivers/mtd/nand/raw/orion_nand.c
+++ b/drivers/mtd/nand/raw/orion_nand.c
@@ -96,6 +96,11 @@ static const struct nand_controller_ops orion_nand_ops = {
.attach_chip = orion_nand_attach_chip,
};
+static void orion_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct orion_nand_info, controller));
+}
+
static int __init orion_nand_probe(struct platform_device *pdev)
{
struct orion_nand_info *info;
@@ -106,15 +111,15 @@ static int __init orion_nand_probe(struct platform_device *pdev)
int ret = 0;
u32 val = 0;
- info = devm_kzalloc(&pdev->dev,
- sizeof(struct orion_nand_info),
- GFP_KERNEL);
+ info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &info->controller, orion_nand_release);
+ if (ret)
+ return ret;
nc = &info->chip;
mtd = nand_to_mtd(nc);
- nand_controller_init(&info->controller);
info->controller.ops = &orion_nand_ops;
nc->controller = &info->controller;
@@ -186,9 +191,16 @@ static int __init orion_nand_probe(struct platform_device *pdev)
return ret;
mtd->name = "orion_nand";
- ret = mtd_device_register(mtd, board->parts, board->nr_parts);
- if (ret)
+ ret = nand_set_release(nc, NULL);
+ if (ret) {
nand_cleanup(nc);
+ return ret;
+ }
+ ret = mtd_device_register(mtd, board->parts, board->nr_parts);
+ if (ret) {
+ nand_disconnect(nc);
+ mtd_put_master(mtd);
+ }
return ret;
}
@@ -202,7 +214,8 @@ static void orion_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
#ifdef CONFIG_OF
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 20/58] mtd: rawnand: socrates: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (18 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 21/58] mtd: rawnand: fsl_upm: " James Hilliard
` (3 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Drain operations before unmapping the FPGA registers.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/socrates_nand.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/socrates_nand.c b/drivers/mtd/nand/raw/socrates_nand.c
index 668584683ce5..508e3afac4f2 100644
--- a/drivers/mtd/nand/raw/socrates_nand.c
+++ b/drivers/mtd/nand/raw/socrates_nand.c
@@ -134,6 +134,11 @@ static const struct nand_controller_ops socrates_ops = {
/*
* Probe for the NAND device.
*/
+static void socrates_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct socrates_nand_host, controller));
+}
+
static int socrates_nand_probe(struct platform_device *ofdev)
{
struct socrates_nand_host *host;
@@ -142,9 +147,12 @@ static int socrates_nand_probe(struct platform_device *ofdev)
int res;
/* Allocate memory for the device structure (and zero it) */
- host = devm_kzalloc(&ofdev->dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ res = devm_nand_controller_init(&ofdev->dev, &host->controller, socrates_nand_release);
+ if (res)
+ return res;
host->io_base = of_iomap(ofdev->dev.of_node, 0);
if (host->io_base == NULL) {
@@ -156,7 +164,6 @@ static int socrates_nand_probe(struct platform_device *ofdev)
mtd = nand_to_mtd(nand_chip);
host->dev = &ofdev->dev;
- nand_controller_init(&host->controller);
host->controller.ops = &socrates_ops;
nand_chip->controller = &host->controller;
@@ -188,11 +195,17 @@ static int socrates_nand_probe(struct platform_device *ofdev)
if (res)
goto out;
+ res = nand_set_release(nand_chip, NULL);
+ if (res) {
+ nand_cleanup(nand_chip);
+ goto out;
+ }
res = mtd_device_register(mtd, NULL, 0);
if (!res)
return res;
- nand_cleanup(nand_chip);
+ nand_disconnect(nand_chip);
+ mtd_put_master(mtd);
out:
iounmap(host->io_base);
@@ -210,9 +223,10 @@ static void socrates_nand_remove(struct platform_device *ofdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
iounmap(host->io_base);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id socrates_nand_match[] =
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 21/58] mtd: rawnand: fsl_upm: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (19 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 20/58] mtd: rawnand: socrates: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 22/58] mtd: rawnand: pasemi: " James Hilliard
` (2 subsequent siblings)
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Also clean up scanned chips when partition registration fails.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_upm.c | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_upm.c b/drivers/mtd/nand/raw/fsl_upm.c
index f4dc990a8da1..bfa4f450d7c6 100644
--- a/drivers/mtd/nand/raw/fsl_upm.c
+++ b/drivers/mtd/nand/raw/fsl_upm.c
@@ -69,7 +69,16 @@ static int fun_chip_init(struct fsl_upm_nand *fun,
if (ret)
goto err;
+ ret = nand_set_release(&fun->chip, NULL);
+ if (ret) {
+ nand_cleanup(&fun->chip);
+ goto err;
+ }
ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(&fun->chip);
+ mtd_put_master(mtd);
+ }
err:
of_node_put(flash_np);
return ret;
@@ -160,6 +169,11 @@ static const struct nand_controller_ops fun_ops = {
.exec_op = fun_exec_op,
};
+static void fun_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct fsl_upm_nand, base));
+}
+
static int fun_probe(struct platform_device *ofdev)
{
struct fsl_upm_nand *fun;
@@ -169,9 +183,12 @@ static int fun_probe(struct platform_device *ofdev)
int size;
int i;
- fun = devm_kzalloc(&ofdev->dev, sizeof(*fun), GFP_KERNEL);
+ fun = kzalloc_obj(*fun);
if (!fun)
return -ENOMEM;
+ ret = devm_nand_controller_init(&ofdev->dev, &fun->base, fun_release);
+ if (ret)
+ return ret;
fun->io_base = devm_platform_get_and_ioremap_resource(ofdev, 0, &io_res);
if (IS_ERR(fun->io_base))
@@ -222,7 +239,6 @@ static int fun_probe(struct platform_device *ofdev)
}
}
- nand_controller_init(&fun->base);
fun->base.ops = &fun_ops;
fun->dev = &ofdev->dev;
@@ -244,7 +260,8 @@ static void fun_remove(struct platform_device *ofdev)
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
}
static const struct of_device_id of_fun_match[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 22/58] mtd: rawnand: pasemi: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (20 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 21/58] mtd: rawnand: fsl_upm: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 23/58] mtd: rawnand: ams-delta: " James Hilliard
2026-11-07 10:16 ` [PATCH 24/58] mtd: rawnand: xway: " James Hilliard
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Release the LPC region and NAND mapping after disconnection.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/pasemi_nand.c | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/pasemi_nand.c b/drivers/mtd/nand/raw/pasemi_nand.c
index 09409b703d93..d00d57cbb0f1 100644
--- a/drivers/mtd/nand/raw/pasemi_nand.c
+++ b/drivers/mtd/nand/raw/pasemi_nand.c
@@ -93,6 +93,11 @@ static const struct nand_controller_ops pasemi_ops = {
.attach_chip = pasemi_attach_chip,
};
+static void pasemi_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct pasemi_ddata, controller));
+}
+
static int pasemi_nand_probe(struct platform_device *ofdev)
{
struct device *dev = &ofdev->dev;
@@ -122,8 +127,10 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
chip = &ddata->chip;
controller = &ddata->controller;
+ err = devm_nand_controller_init(dev, controller, pasemi_nand_release);
+ if (err)
+ return err;
controller->ops = &pasemi_ops;
- nand_controller_init(controller);
chip->controller = controller;
pasemi_nand_mtd = nand_to_mtd(chip);
@@ -136,7 +143,7 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
if (!chip->legacy.IO_ADDR_R) {
err = -EIO;
- goto out_mtd;
+ goto out;
}
pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL);
@@ -174,10 +181,15 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
if (err)
goto out_lpc;
+ err = nand_set_release(chip, NULL);
+ if (err)
+ goto out_cleanup_nand;
if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) {
dev_err(dev, "Unable to register MTD device\n");
err = -ENODEV;
- goto out_cleanup_nand;
+ nand_disconnect(chip);
+ mtd_put_master(pasemi_nand_mtd);
+ goto out_lpc;
}
dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n", &res,
@@ -191,8 +203,6 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
release_region(ddata->lpcctl, 4);
out_ior:
iounmap(chip->legacy.IO_ADDR_R);
- out_mtd:
- kfree(ddata);
out:
return err;
}
@@ -210,14 +220,13 @@ static void pasemi_nand_remove(struct platform_device *ofdev)
/* Release resources, unregister device */
ret = mtd_device_unregister(pasemi_nand_mtd);
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
release_region(ddata->lpcctl, 4);
iounmap(chip->legacy.IO_ADDR_R);
- /* Free the MTD device structure */
- kfree(ddata);
+ mtd_put_master(pasemi_nand_mtd);
}
static const struct of_device_id pasemi_nand_match[] =
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 23/58] mtd: rawnand: ams-delta: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (21 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 22/58] mtd: rawnand: pasemi: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
2026-11-07 10:16 ` [PATCH 24/58] mtd: rawnand: xway: " James Hilliard
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Apply write protection after unregister notifiers finish their writes.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/ams-delta.c | 29 +++++++++++++++++++++--------
1 file changed, 21 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/ams-delta.c b/drivers/mtd/nand/raw/ams-delta.c
index fb2b7db70297..ca3fd966c4ff 100644
--- a/drivers/mtd/nand/raw/ams-delta.c
+++ b/drivers/mtd/nand/raw/ams-delta.c
@@ -233,6 +233,11 @@ static const struct nand_controller_ops gpio_nand_ops = {
/*
* Main initialization routine
*/
+static void gpio_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct gpio_nand, base));
+}
+
static int gpio_nand_probe(struct platform_device *pdev)
{
struct gpio_nand_platdata *pdata = dev_get_platdata(&pdev->dev);
@@ -250,10 +255,12 @@ static int gpio_nand_probe(struct platform_device *pdev)
}
/* Allocate memory for MTD device structure and private data */
- priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_nand),
- GFP_KERNEL);
+ priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &priv->base, gpio_nand_release);
+ if (err)
+ return err;
this = &priv->nand_chip;
@@ -355,7 +362,6 @@ static int gpio_nand_probe(struct platform_device *pdev)
/* Initialize the NAND controller object embedded in gpio_nand. */
priv->base.ops = &gpio_nand_ops;
- nand_controller_init(&priv->base);
this->controller = &priv->base;
/*
@@ -382,9 +388,15 @@ static int gpio_nand_probe(struct platform_device *pdev)
return err;
/* Register the partitions */
- err = mtd_device_register(mtd, partitions, num_partitions);
+ err = nand_set_release(this, NULL);
if (err)
goto err_nand_cleanup;
+ err = mtd_device_register(mtd, partitions, num_partitions);
+ if (err) {
+ nand_disconnect(this);
+ mtd_put_master(mtd);
+ return err;
+ }
return 0;
@@ -403,13 +415,14 @@ static void gpio_nand_remove(struct platform_device *pdev)
struct mtd_info *mtd = nand_to_mtd(&priv->nand_chip);
int ret;
- /* Apply write protection */
- gpiod_set_value(priv->gpiod_nwp, 1);
-
/* Unregister device */
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(mtd_to_nand(mtd));
+ nand_disconnect(mtd_to_nand(mtd));
+
+ /* Apply write protection after draining outstanding operations. */
+ gpiod_set_value(priv->gpiod_nwp, 1);
+ mtd_put_master(mtd);
}
#ifdef CONFIG_OF
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread* [PATCH 24/58] mtd: rawnand: xway: retain open-handle state on removal
2026-11-07 10:15 [PATCH 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (22 preceding siblings ...)
2026-11-07 10:16 ` [PATCH 23/58] mtd: rawnand: ams-delta: " James Hilliard
@ 2026-11-07 10:16 ` James Hilliard
23 siblings, 0 replies; 25+ messages in thread
From: James Hilliard @ 2026-11-07 10:16 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Brian Norris, Boris Brezillon, Arseniy Krasnov, Chen-Yu Tsai,
Jernej Skrabec, Samuel Holland, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, David Woodhouse, Mike Rapoport,
Maxime Coquelin, Alexandre Torgue, Christophe Kerello,
Jason Roberts, Michal Simek, Boris Brezillon, Mason Yang,
Ramuthevar Vadivel Murugan, Stefan Agner, Lucas Stach,
Thierry Reding, Jonathan Hunter, Dmitry Osipenko,
Matthias Brugger, AngeloGioacchino Del Regno,
Jorge Ramirez-Ortiz, Heiko Stuebner, Yifeng Zhao, Nicolas Ferre,
Alexandre Belloni, Claudiu Beznea, Keguang Zhang, Jacky Huang,
Shan-Chun Hung, Frank Li, Sascha Hauer, Pengutronix Kernel Team,
Fabio Estevam, Han Xu, Liang Yang, Neil Armstrong, Kevin Hilman,
Jerome Brunet, Martin Blumenstingl, Manivannan Sadhasivam,
Kamal Dasu, Broadcom internal kernel review list,
Vladimir Zapolskiy, Piotr Wojtaszczyk, Paul Cercueil,
Harvey Hunt, Maxim Levitsky, Michael Walle, Srinivas Kandagatla
Cc: Tze Yee Ng, linux-mtd, linux-kernel, linux-arm-kernel,
linux-sunxi, linux-tegra, linux-mediatek, linux-rockchip,
linux-mips, imx, linux-renesas-soc, linux-amlogic, linux-arm-msm,
James Hilliard
Keep chip metadata and controller storage alive until the final MTD
reference is released. Unregister and disconnect each published chip
before releasing the resources used by its hardware operations.
Retain the embedded controller and chip across platform-device removal.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/xway_nand.c | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/xway_nand.c b/drivers/mtd/nand/raw/xway_nand.c
index af84395dc66e..4686f28797bc 100644
--- a/drivers/mtd/nand/raw/xway_nand.c
+++ b/drivers/mtd/nand/raw/xway_nand.c
@@ -8,6 +8,7 @@
#include <linux/mtd/rawnand.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/slab.h>
#include <lantiq_soc.h>
@@ -162,6 +163,11 @@ static const struct nand_controller_ops xway_nand_ops = {
/*
* Probe for the NAND device.
*/
+static void xway_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct xway_nand_data, controller));
+}
+
static int xway_nand_probe(struct platform_device *pdev)
{
struct xway_nand_data *data;
@@ -171,10 +177,12 @@ static int xway_nand_probe(struct platform_device *pdev)
u32 cs_flag = 0;
/* Allocate memory for the device structure (and zero it) */
- data = devm_kzalloc(&pdev->dev, sizeof(struct xway_nand_data),
- GFP_KERNEL);
+ data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &data->controller, xway_nand_release);
+ if (err)
+ return err;
data->nandaddr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->nandaddr))
@@ -192,7 +200,6 @@ static int xway_nand_probe(struct platform_device *pdev)
data->chip.legacy.read_byte = xway_read_byte;
data->chip.legacy.chip_delay = 30;
- nand_controller_init(&data->controller);
data->controller.ops = &xway_nand_ops;
data->chip.controller = &data->controller;
@@ -228,9 +235,16 @@ static int xway_nand_probe(struct platform_device *pdev)
if (err)
return err;
- err = mtd_device_register(mtd, NULL, 0);
- if (err)
+ err = nand_set_release(&data->chip, NULL);
+ if (err) {
nand_cleanup(&data->chip);
+ return err;
+ }
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err) {
+ nand_disconnect(&data->chip);
+ mtd_put_master(mtd);
+ }
return err;
}
@@ -246,7 +260,8 @@ static void xway_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id xway_nand_match[] = {
--
2.53.0
_______________________________________________
linux-amlogic mailing list
linux-amlogic@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-amlogic
^ permalink raw reply [flat|nested] 25+ messages in thread