* [PATCH v2 01/58] nvmem: retain provider context through final device release
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 02/58] driver core: retain the owner of device release callbacks James Hilliard
` (56 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Srinivas Kandagatla, Tze Yee Ng
An NVMEM provider can keep its read/write context in an object separate
from its parent device. Retaining the parent does not keep that object
alive, and nvmem_unregister() can return while consumers still hold the
provider. A device reference can also delay release after the last
consumer is gone.
Add optional paired callbacks to take a reference to the private context
before publishing the provider and drop it from the final device release
callback. Balance the reference on registration errors as well. Require
both callbacks together. Pin their module before taking the context
reference and release it only after the final put callback returns; the
context callbacks can belong to a different module than the provider.
MTD OTP providers need this ownership because they are children of the
physical flash device, while their callback context is the separately
allocated MTD master.
Cc: Srinivas Kandagatla <srini@kernel.org>
Cc: Tze Yee Ng <tze.yee.ng@altera.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/nvmem/core.c | 18 +++++++++++++++++-
drivers/nvmem/internals.h | 2 ++
include/linux/nvmem-provider.h | 12 ++++++++++++
3 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0556d140170a..8846354ae063 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -541,6 +541,9 @@ static void nvmem_release(struct device *dev)
ida_free(&nvmem_ida, nvmem->id);
gpiod_put(nvmem->wp_gpio);
+ if (nvmem->ops->priv_put)
+ nvmem->ops->priv_put(nvmem->priv);
+ module_put(nvmem->ops->priv_owner);
kfree(nvmem->ops);
kfree(nvmem);
}
@@ -896,6 +899,9 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (!config->reg_read && !config->reg_write)
return ERR_PTR(-EINVAL);
+ if (!!config->priv_get != !!config->priv_put)
+ return ERR_PTR(-EINVAL);
+
nvmem = kzalloc_obj(*nvmem);
if (!nvmem)
return ERR_PTR(-ENOMEM);
@@ -922,6 +928,17 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
device_initialize(&nvmem->dev);
+ nvmem->priv = config->priv;
+ if (config->priv_get) {
+ if (!try_module_get(config->priv_owner)) {
+ rval = -ENODEV;
+ goto err_put_device;
+ }
+ ops->priv_owner = config->priv_owner;
+ config->priv_get(config->priv);
+ ops->priv_put = config->priv_put;
+ }
+
if (!config->ignore_wp)
nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp",
GPIOD_OUT_HIGH);
@@ -945,7 +962,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->word_size = config->word_size ?: 1;
nvmem->size = config->size;
nvmem->root_only = config->root_only;
- nvmem->priv = config->priv;
nvmem->type = config->type;
nvmem->keepout = config->keepout;
nvmem->nkeepout = config->nkeepout;
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4e610deeaa7b..21e26b73c160 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -11,6 +11,8 @@
struct nvmem_operations {
nvmem_reg_read_t reg_read;
nvmem_reg_write_t reg_write;
+ void (*priv_put)(void *priv);
+ struct module *priv_owner;
};
struct nvmem_device {
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 6063fe5b7784..b674774ee156 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -98,6 +98,9 @@ struct nvmem_cell_info {
* @word_size: Minimum read/write access granularity.
* @stride: Minimum read/write access stride.
* @priv: User context passed to read/write callbacks.
+ * @priv_get: Optional callback taking a reference to @priv before publication.
+ * @priv_put: Matching callback releasing @priv at final device release.
+ * @priv_owner: Module containing @priv_get and @priv_put, or NULL if built-in.
* @ignore_wp: Write Protect pin is managed by the provider.
* @layout: Fixed layout associated with this nvmem device.
*
@@ -107,6 +110,12 @@ struct nvmem_cell_info {
*
* Note: Specifying name and setting id to -1 implies a unique device
* whose name is provided as-is (kept unaltered).
+ *
+ * Note: @priv_get and @priv_put must be supplied together. Each @priv_get
+ * call is balanced by one @priv_put, including on registration failure.
+ * The put can run after nvmem_unregister() returns, when consumers and
+ * device references have gone away. The core holds @priv_owner until
+ * @priv_put returns, independently of the provider's @owner.
*/
struct nvmem_config {
struct device *dev;
@@ -132,6 +141,9 @@ struct nvmem_config {
int word_size;
int stride;
void *priv;
+ void (*priv_get)(void *priv);
+ void (*priv_put)(void *priv);
+ struct module *priv_owner;
/* To be only used by old driver/misc/eeprom drivers */
bool compat;
struct device *base_dev;
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 02/58] driver core: retain the owner of device release callbacks
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-10 7:44 ` [PATCH v2 01/58] nvmem: retain provider context through final device release James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 18:50 ` Greg Kroah-Hartman
2026-11-10 7:44 ` [PATCH v2 03/58] mtd: add opt-in lifetime ownership for backing objects James Hilliard
` (55 subsequent siblings)
57 siblings, 1 reply; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, driver-core
Device references can outlive the driver or subsystem operation that
unregistered the device. A modular release callback needs its code to
remain loaded until it returns. Dropping its last module reference inside
the callback permits concurrent unload before the function epilogue has
finished.
Add an optional release_owner to struct device. device_initialize() takes
the reference before the device can be published. The built-in device
core caches the owner before invoking the release callback and drops the
reference after the callback returns, allowing the callback to free the
device itself. This also covers initialized devices that were never added
and device_add() failures.
MTD will use this to keep its release callback available after retained
device references outlive driver unbind.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rafael J. Wysocki <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: driver-core@lists.linux.dev
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/base/core.c | 9 +++++++++
include/linux/device.h | 4 ++++
2 files changed, 13 insertions(+)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4c0c373998a1..03f8bf69219a 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2617,6 +2617,7 @@ static void device_release(struct kobject *kobj)
{
struct device *dev = kobj_to_dev(kobj);
struct device_private *p = dev->p;
+ struct module *owner = dev->release_owner;
/*
* Some platform devices are driven without driver attached
@@ -2642,6 +2643,7 @@ static void device_release(struct kobject *kobj)
WARN(1, KERN_ERR "Device '%s' does not have a release() function, it is broken and must be fixed. See Documentation/core-api/kobject.rst.\n",
dev_name(dev));
kfree(p);
+ module_put(owner);
}
static const struct ns_common *device_namespace(const struct kobject *kobj)
@@ -3225,11 +3227,18 @@ static void klist_children_put(struct klist_node *n)
* approach is to use kzalloc() to allocate the structure containing
* @dev.
*
+ * If @dev->release_owner is set, the caller must keep that module loaded
+ * until this function acquires a reference. The core drops the reference
+ * after the release callback returns, including when device_add() fails.
+ * A module whose initialization fails must still drain its devices before
+ * returning the error, since module references do not prevent that unload.
+ *
* NOTE: Use put_device() to give up your reference instead of freeing
* @dev directly once you have called this function.
*/
void device_initialize(struct device *dev)
{
+ __module_get(dev->release_owner);
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
diff --git a/include/linux/device.h b/include/linux/device.h
index aee79fd6b32b..cd39ebf47ee7 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -683,6 +683,9 @@ enum struct_device_flags {
* @release: Callback to free the device after all references have
* gone away. This should be set by the allocator of the
* device (i.e. the bus driver that discovered the device).
+ * @release_owner: Optional module containing the release callback. Set before
+ * device_initialize() and leave unchanged for the device's lifetime.
+ * The core holds a reference until the release callback returns.
* @iommu_group: IOMMU group the device belongs to.
* @iommu: Per device generic IOMMU runtime data
* @physical_location: Describes physical location of the device connection
@@ -787,6 +790,7 @@ struct device {
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
+ struct module *release_owner;
struct iommu_group *iommu_group;
struct dev_iommu *iommu;
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* Re: [PATCH v2 02/58] driver core: retain the owner of device release callbacks
2026-11-10 7:44 ` [PATCH v2 02/58] driver core: retain the owner of device release callbacks James Hilliard
@ 2026-11-10 18:50 ` Greg Kroah-Hartman
0 siblings, 0 replies; 60+ messages in thread
From: Greg Kroah-Hartman @ 2026-11-10 18:50 UTC (permalink / raw)
To: James Hilliard
Cc: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
linux-mtd, linux-kernel, Rafael J. Wysocki, Danilo Krummrich,
driver-core
On Tue, Nov 10, 2026 at 12:44:05AM -0700, James Hilliard wrote:
> Device references can outlive the driver or subsystem operation that
> unregistered the device. A modular release callback needs its code to
> remain loaded until it returns. Dropping its last module reference inside
> the callback permits concurrent unload before the function epilogue has
> finished.
>
> Add an optional release_owner to struct device. device_initialize() takes
> the reference before the device can be published. The built-in device
> core caches the owner before invoking the release callback and drops the
> reference after the callback returns, allowing the callback to free the
> device itself. This also covers initialized devices that were never added
> and device_add() failures.
Please no, we have tried this in the past and it's not good. Devices
are data, which is different from code.
> MTD will use this to keep its release callback available after retained
> device references outlive driver unbind.
This isn't a good idea, dig through the archives and see what went wrong
before please.
As a hint, look at networking, no module reference counts anywhere!
That's the proper way to do it.
thanks,
greg k-h
^ permalink raw reply [flat|nested] 60+ messages in thread
* [PATCH v2 03/58] mtd: add opt-in lifetime ownership for backing objects
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-10 7:44 ` [PATCH v2 01/58] nvmem: retain provider context through final device release James Hilliard
2026-11-10 7:44 ` [PATCH v2 02/58] driver core: retain the owner of device release callbacks James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 04/58] mtd: retain virtual concatenations across component removal James Hilliard
` (54 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Srinivas Kandagatla, Tze Yee Ng
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.
The device core owns the device callback reference and drops it after
the callback returns, including on registration failure.
Distinguish registration from retained references so an unregistered
node cannot be opened again. Balance master references for direct
opens even with MTD_PARTITIONED_MASTER disabled. Keep that decision
independent of later master publication, so an earlier partition
handle cannot drop an unacquired master reference.
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.
Cc: Srinivas Kandagatla <srini@kernel.org>
Cc: Tze Yee Ng <tze.yee.ng@altera.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/mtdcore.c | 194 +++++++++++++++++++++++++++++++++++++++++-------
include/linux/mtd/mtd.h | 16 ++++
2 files changed, 183 insertions(+), 27 deletions(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 30b16f135f07..1c31e2a5fbe0 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,117 @@ 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. If the module is already unloading, wait for
+ * the remaining 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);
+ }
}
/*
- * 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 +221,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 +700,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;
@@ -700,10 +788,13 @@ static struct device_node *mtd_find_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
@@ -746,16 +837,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)
@@ -802,6 +905,7 @@ int add_mtd_device(struct mtd_info *mtd)
error = dev_set_name(&mtd->dev, "mtd%d", i);
if (error)
goto fail_devname;
+ mtd->dev.release_owner = THIS_MODULE;
dev_set_drvdata(&mtd->dev, mtd);
mtd_set_of_node(mtd, mtd_find_of_node(mtd));
error = device_register(&mtd->dev);
@@ -819,6 +923,7 @@ int add_mtd_device(struct mtd_info *mtd)
"mtd%dro", i);
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)
@@ -836,29 +941,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;
}
@@ -879,7 +993,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;
}
@@ -889,6 +1003,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;
@@ -950,6 +1065,16 @@ 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);
+}
+
+static void mtd_otp_nvmem_put(void *priv)
+{
+ mtd_put_lifetime(priv);
+}
+
static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
const char *compatible,
int size,
@@ -966,7 +1091,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;
@@ -975,6 +1100,11 @@ 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;
+ config.priv_owner = THIS_MODULE;
+ }
nvmem = nvmem_register(&config);
/* Just ignore if there is no NVMEM support in the kernel */
@@ -1332,8 +1462,12 @@ EXPORT_SYMBOL_GPL(get_mtd_device);
int __get_mtd_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
+ bool master_ref = mtd == master || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER);
int err;
+ if (!mtd->registered)
+ return -ENODEV;
+
if (master->_get_device) {
err = master->_get_device(mtd);
if (err)
@@ -1346,13 +1480,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_ref)
kref_get(&master->refcnt);
return 0;
@@ -1437,8 +1572,10 @@ EXPORT_SYMBOL_GPL(put_mtd_device);
void __put_mtd_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
+ bool master_ref = mtd == master || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER);
/* 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 */
@@ -1449,12 +1586,14 @@ void __put_mtd_device(struct mtd_info *mtd)
mtd = parent;
}
- if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+ if (master_ref)
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);
@@ -2671,6 +2810,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 cd7b4671c456..eb9fd9ea0c79 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.
@@ -225,6 +226,10 @@ struct mtd_part {
* sub-partitions.
* @suspended: set to 1 when the device is suspended, 0 otherwise
* @unregistering: removal has started; protected by partitions_lock
+ * @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
@@ -235,6 +240,10 @@ struct mtd_master {
struct mutex chrdev_lock;
unsigned int suspended : 1;
bool unregistering;
+ struct kref life_refcnt;
+ void (*release)(struct mtd_info *mtd);
+ struct module *release_owner;
+ struct completion *release_done;
};
struct mtd_info {
@@ -382,6 +391,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;
@@ -683,6 +696,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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 04/58] mtd: retain virtual concatenations across component removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (2 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 03/58] mtd: add opt-in lifetime ownership for backing objects James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 05/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
` (53 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Yuqiang Sun,
Amit Kumar Mahapatra, Luca Ceresoli, Bernhard Frauendienst,
Gustavo A. R. Silva, Kees Cook, linux-hardening
Virtual concatenation keeps unregistered partition objects outside the
normal partition list. They neither retain their backing masters nor get
removed by partition-registration failure cleanup. The teardown path also
compares a master against its hidden children, leaving stale pointers when
one of the physical devices disappears.
Give staged components explicit ownership of their backing objects and
retain each published concatenation's component set until its final MTD
release. Register the object returned by mtd_concat_create() directly,
instead of copying its embedded device. An old handle then continues to
refer to its original components after a replacement master is probed.
Serialize the staging list and publication across physical controllers.
Store components in DT order and publish only after every contributing
master finishes partition registration. Detach affected joins while holding
the staging lock, then unregister them without it. Use a separate partition
lock class for virtual masters so a physical partition can remove its joins
without nesting locks from the same class.
Unwind hidden components along with ordinary partitions on registration
failure. Keep the other masters' staged components available for a later
probe, and advance the partition offset past a hidden component before
processing an appended partition.
Fixes: 43db6366fc2d ("mtd: Add driver for concatenating devices")
Cc: Yuqiang Sun <izaiahsun@gmail.com>
Cc: Amit Kumar Mahapatra <amit.kumar-mahapatra@amd.com>
Cc: Luca Ceresoli <luca.ceresoli@bootlin.com>
Cc: Bernhard Frauendienst <kernel@nospam.obeliks.de>
Cc: Gustavo A. R. Silva <gustavoars@kernel.org>
Cc: Kees Cook <kees@kernel.org>
Cc: linux-hardening@vger.kernel.org
Link: https://lore.kernel.org/r/20261103143953.69642-1-izaiahsun@gmail.com/
Link: https://lore.kernel.org/r/sashiko-outbox-195551@kernel.org/
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/mtd_virt_concat.c | 513 +++++++++++++++++++++---------------------
drivers/mtd/mtdcore.c | 9 +-
drivers/mtd/mtdcore.h | 3 +
drivers/mtd/mtdpart.c | 14 +-
include/linux/mtd/concat.h | 18 +-
5 files changed, 283 insertions(+), 274 deletions(-)
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
index da4277ced4d6..1c797a4afb9b 100644
--- a/drivers/mtd/mtd_virt_concat.c
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -7,346 +7,339 @@
*/
#include <linux/device.h>
+#include <linux/lockdep.h>
+#include <linux/module.h>
#include <linux/mtd/mtd.h>
-#include "mtdcore.h"
-#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
+#include <linux/mutex.h>
#include <linux/of.h>
-#include <linux/of_platform.h>
#include <linux/slab.h>
-#include <linux/mtd/concat.h>
+
+#include "mtdcore.h"
#define CONCAT_PROP "part-concat-next"
-#define CONCAT_POSTFIX "concat"
-#define MIN_DEV_PER_CONCAT 1
-static LIST_HEAD(concat_node_list);
+struct mtd_virt_concat_part {
+ struct device_node *node;
+ struct mtd_info *mtd;
+ bool ready;
+};
+
+struct mtd_virt_concat_device {
+ struct list_head node;
+ struct mtd_info *mtd;
+};
-/**
- * struct mtd_virt_concat_node - components of a concatenation
- * @head: List handle
- * @count: Number of nodes
- * @nodes: Pointer to the nodes (partitions) to concatenate
- * @concat: Concatenation container
- */
struct mtd_virt_concat_node {
- struct list_head head;
+ struct list_head node;
+ struct mtd_virt_concat_device *device;
unsigned int count;
- struct mtd_concat *concat;
- struct device_node *nodes[] __counted_by(count);
+ struct mtd_virt_concat_part parts[] __counted_by(count);
};
-/**
- * mtd_is_part_concat - Check if the device is already part
- * of a concatenated device
- * @dev: pointer to 'device_node'
- *
- * Return: true if the device is already part of a concatenation,
- * false otherwise.
- */
-static bool mtd_is_part_concat(struct device_node *dev)
+static LIST_HEAD(concat_node_list);
+static DEFINE_MUTEX(concat_lock);
+static struct lock_class_key concat_partitions_lock_key;
+
+static bool mtd_is_part_concat(struct device_node *node)
{
struct mtd_virt_concat_node *item;
- int idx;
+ unsigned int i;
- list_for_each_entry(item, &concat_node_list, head) {
- for (idx = 0; idx < item->count; idx++) {
- if (item->nodes[idx] == dev)
+ list_for_each_entry(item, &concat_node_list, node)
+ for (i = 0; i < item->count; i++)
+ if (item->parts[i].node == node)
return true;
- }
- }
return false;
}
-static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat)
+/* Hidden partitions have no registered device to own their storage. */
+static void mtd_virt_concat_part_release(struct kref *ref)
+{
+ struct mtd_info *mtd = container_of(ref, struct mtd_info, refcnt);
+ struct mtd_info *master = mtd_get_master(mtd);
+ struct mtd_info *parent = mtd->parent;
+
+ of_node_put(mtd_get_of_node(mtd));
+ release_mtd_partition(mtd);
+ if (parent != master)
+ put_device(&parent->dev);
+ mtd_put_lifetime(master);
+}
+
+static void mtd_virt_concat_part_put(struct mtd_info *mtd)
+{
+ kref_put(&mtd->refcnt, mtd_virt_concat_part_release);
+}
+
+static void mtd_virt_concat_release(struct mtd_info *mtd)
{
+ struct mtd_concat *concat = container_of(mtd, struct mtd_concat, mtd);
int i;
for (i = 0; i < concat->num_subdev; i++)
- put_mtd_device(concat->subdev[i]);
+ mtd_virt_concat_part_put(concat->subdev[i]);
+ kfree(mtd->priv);
+ kfree(mtd->name);
+ mtd_concat_destroy(mtd);
}
-void mtd_virt_concat_destroy_joins(void)
+static void mtd_virt_concat_unregister(struct list_head *devices)
{
- struct mtd_virt_concat_node *item, *tmp;
- struct mtd_info *mtd;
+ struct mtd_virt_concat_device *device, *next;
- list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
- mtd = &item->concat->mtd;
- if (item->concat) {
- mtd_device_unregister(mtd);
- kfree(mtd->name);
- mtd_virt_concat_put_mtd_devices(item->concat);
- mtd_concat_destroy(mtd);
- }
+ list_for_each_entry_safe(device, next, devices, node) {
+ list_del(&device->node);
+ mtd_device_unregister(device->mtd);
+ mtd_put_master(device->mtd);
}
}
-/**
- * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object
- * @mtd: pointer to 'mtd_info'
- *
- * Return: 0 on success, -error otherwise.
- */
+static bool mtd_virt_concat_contains(struct mtd_info *part, struct mtd_info *mtd)
+{
+ for (; part; part = part->parent)
+ if (part == mtd)
+ return true;
+ return false;
+}
+
int mtd_virt_concat_destroy(struct mtd_info *mtd)
{
- struct mtd_info *child, *master = mtd_get_master(mtd);
- struct mtd_virt_concat_node *item, *tmp;
- struct mtd_concat *concat;
- int idx, ret = 0;
- bool is_mtd_found;
-
- list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
- is_mtd_found = false;
-
- /* Find the concat item that hold the mtd device */
- for (idx = 0; idx < item->count; idx++) {
- if (item->nodes[idx] == mtd->dev.of_node) {
- is_mtd_found = true;
- break;
- }
- }
- if (!is_mtd_found)
- continue;
- concat = item->concat;
-
- /*
- * Since this concatenated device is being removed, retrieve
- * all MTD devices that are part of it and register them
- * individually.
- */
- for (idx = 0; idx < concat->num_subdev; idx++) {
- child = concat->subdev[idx];
- if (child->dev.of_node != mtd->dev.of_node) {
- ret = add_mtd_device(child);
- if (ret)
- goto out;
+ struct mtd_virt_concat_node *item;
+ LIST_HEAD(devices);
+ unsigned int i;
+
+ /* A virtual master cannot itself be a DT concatenation component. */
+ if (mtd_get_master(mtd)->master.release == mtd_virt_concat_release)
+ return 0;
+
+ mutex_lock(&concat_lock);
+ list_for_each_entry(item, &concat_node_list, node) {
+ for (i = 0; i < item->count; i++) {
+ struct mtd_virt_concat_part *part = &item->parts[i];
+
+ if (!mtd_virt_concat_contains(part->mtd, mtd))
+ continue;
+ if (item->device) {
+ list_add_tail(&item->device->node, &devices);
+ item->device = NULL;
}
+ mtd_virt_concat_part_put(part->mtd);
+ part->mtd = NULL;
+ part->ready = false;
}
- /* Destroy the concat */
- if (concat->mtd.name) {
- del_mtd_device(&concat->mtd);
- kfree(concat->mtd.name);
- mtd_virt_concat_put_mtd_devices(item->concat);
- mtd_concat_destroy(&concat->mtd);
- }
-
- for (idx = 0; idx < item->count; idx++)
- of_node_put(item->nodes[idx]);
-
- kfree(item);
}
+ mutex_unlock(&concat_lock);
+
+ /* Unregistration takes the virtual master's partition lock. */
+ mtd_virt_concat_unregister(&devices);
return 0;
-out:
- mutex_lock(&master->master.partitions_lock);
- list_del(&child->part.node);
- mutex_unlock(&master->master.partitions_lock);
- kfree(mtd->name);
- kfree(mtd);
+}
- return ret;
+static void mtd_virt_concat_free_item(struct mtd_virt_concat_node *item)
+{
+ unsigned int i;
+
+ for (i = 0; i < item->count; i++)
+ of_node_put(item->parts[i].node);
+ kfree(item);
}
-/**
- * mtd_virt_concat_create_item - Create a concat item
- * @parts: pointer to 'device_node'
- * @count: number of mtd devices that make up
- * the concatenated device.
- *
- * Return: 0 on success, -error otherwise.
- */
-static int mtd_virt_concat_create_item(struct device_node *parts,
- unsigned int count)
+static int mtd_virt_concat_create_item(struct device_node *node, unsigned int count)
{
struct mtd_virt_concat_node *item;
- struct mtd_concat *concat;
- int i;
+ unsigned int i, j;
- for (i = 0; i < (count - 1); i++) {
- if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i)))
- return 0;
- }
-
- item = kzalloc_flex(*item, nodes, count, GFP_KERNEL);
+ item = kzalloc_flex(*item, parts, count, GFP_KERNEL);
if (!item)
return -ENOMEM;
-
item->count = count;
+ item->parts[0].node = of_node_get(node);
- /*
- * The partition in which "part-concat-next" property
- * is defined is the first device in the list of concat
- * devices.
- */
- item->nodes[0] = parts;
+ for (i = 1; i < count; i++) {
+ struct device_node *part = of_parse_phandle(node, CONCAT_PROP, i - 1);
- for (i = 1; i < count; i++)
- item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1));
-
- concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL);
- if (!concat) {
- kfree(item);
- return -ENOMEM;
+ item->parts[i].node = part;
+ if (!part || mtd_is_part_concat(part))
+ goto out;
+ for (j = 0; j < i; j++)
+ if (item->parts[j].node == part)
+ goto out;
}
-
- item->concat = concat;
-
- list_add_tail(&item->head, &concat_node_list);
-
+ list_add_tail(&item->node, &concat_node_list);
return 0;
+out:
+ mtd_virt_concat_free_item(item);
+ return -EINVAL;
}
-void mtd_virt_concat_destroy_items(void)
+void mtd_virt_concat_destroy_joins(void)
{
- struct mtd_virt_concat_node *item, *temp;
- int i;
+ struct mtd_virt_concat_node *item;
+ LIST_HEAD(devices);
+ unsigned int i;
+
+ mutex_lock(&concat_lock);
+ list_for_each_entry(item, &concat_node_list, node) {
+ if (item->device) {
+ list_add_tail(&item->device->node, &devices);
+ item->device = NULL;
+ }
+ for (i = 0; i < item->count; i++) {
+ if (item->parts[i].mtd)
+ mtd_virt_concat_part_put(item->parts[i].mtd);
+ item->parts[i].mtd = NULL;
+ item->parts[i].ready = false;
+ }
+ }
+ mutex_unlock(&concat_lock);
+ mtd_virt_concat_unregister(&devices);
+}
- list_for_each_entry_safe(item, temp, &concat_node_list, head) {
- for (i = 0; i < item->count; i++)
- of_node_put(item->nodes[i]);
+void mtd_virt_concat_destroy_items(void)
+{
+ struct mtd_virt_concat_node *item, *next;
- kfree(item);
+ guard(mutex)(&concat_lock);
+ list_for_each_entry_safe(item, next, &concat_node_list, node) {
+ list_del(&item->node);
+ mtd_virt_concat_free_item(item);
}
}
-/**
- * mtd_virt_concat_add - Add a mtd device to the concat list
- * @mtd: pointer to 'mtd_info'
- *
- * Return: true on success, false otherwise.
- */
-bool mtd_virt_concat_add(struct mtd_info *mtd)
+int mtd_virt_concat_add(struct mtd_info *mtd)
{
struct mtd_virt_concat_node *item;
- struct mtd_concat *concat;
- int idx;
-
- list_for_each_entry(item, &concat_node_list, head) {
- concat = item->concat;
- for (idx = 0; idx < item->count; idx++) {
- if (item->nodes[idx] == mtd->dev.of_node) {
- concat->subdev[concat->num_subdev++] = mtd;
- return true;
- }
+ unsigned int i;
+
+ guard(mutex)(&concat_lock);
+ list_for_each_entry(item, &concat_node_list, node) {
+ for (i = 0; i < item->count; i++) {
+ struct mtd_virt_concat_part *part = &item->parts[i];
+
+ if (part->node != mtd_get_of_node(mtd))
+ continue;
+ if (part->mtd)
+ return -EBUSY;
+ kref_init(&mtd->refcnt);
+ /* The master need not have a registered device. */
+ mtd_get_lifetime(mtd_get_master(mtd));
+ if (mtd->parent != mtd_get_master(mtd))
+ get_device(&mtd->parent->dev);
+ of_node_get(mtd_get_of_node(mtd));
+ part->mtd = mtd;
+ return 1;
}
}
- return false;
+ return 0;
}
-/**
- * mtd_virt_concat_node_create - List all the concatenations found in DT
- *
- * Return: 0 on success, -error otherwise.
- */
int mtd_virt_concat_node_create(void)
{
- struct device_node *parts = NULL;
- int ret = 0, count = 0;
+ struct device_node *node;
+ int ret, count;
- /* List all the concatenations found in DT */
- do {
- parts = of_find_node_with_property(parts, CONCAT_PROP);
- if (!of_device_is_available(parts))
+ guard(mutex)(&concat_lock);
+ for_each_node_with_property(node, CONCAT_PROP) {
+ if (!of_device_is_available(node) || mtd_is_part_concat(node))
continue;
-
- if (mtd_is_part_concat(parts))
- continue;
-
- count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
- if (count < MIN_DEV_PER_CONCAT)
+ count = of_count_phandle_with_args(node, CONCAT_PROP, NULL);
+ if (count < 1)
continue;
-
- /*
- * The partition in which "part-concat-next" property is defined
- * is also part of the concat device, so increament count by 1.
- */
- count++;
-
- ret = mtd_virt_concat_create_item(parts, count);
+ ret = mtd_virt_concat_create_item(node, count + 1);
if (ret) {
- of_node_put(parts);
- goto destroy_items;
+ of_node_put(node);
+ return ret;
}
- } while (parts);
+ }
+ return 0;
+}
+static int mtd_virt_concat_register(struct mtd_virt_concat_node *item)
+{
+ struct mtd_virt_concat_device *device;
+ struct mtd_info **parts, *mtd;
+ size_t name_size = sizeof("concat");
+ unsigned int i;
+ char *name, *p;
+ int ret;
+
+ parts = kmalloc_array(item->count, sizeof(*parts), GFP_KERNEL);
+ if (!parts)
+ return -ENOMEM;
+ for (i = 0; i < item->count; i++) {
+ parts[i] = item->parts[i].mtd;
+ name_size += strlen(parts[i]->name) + 1;
+ }
+ name = kmalloc(name_size, GFP_KERNEL);
+ if (!name) {
+ ret = -ENOMEM;
+ goto out_parts;
+ }
+ p = name;
+ for (i = 0; i < item->count; i++)
+ p += sprintf(p, "%s-", parts[i]->name);
+ strscpy(p, "concat", name_size - (p - name));
+ device = kzalloc_obj(*device);
+ if (!device) {
+ ret = -ENOMEM;
+ goto out_name;
+ }
+ mtd = mtd_concat_create(parts, item->count, name);
+ if (!mtd) {
+ ret = -ENXIO;
+ goto out_device;
+ }
+ for (i = 0; i < item->count; i++)
+ kref_get(&parts[i]->refcnt);
+ mtd->dev.parent = parts[0]->dev.parent;
+ mtd->owner = THIS_MODULE;
+ mtd->priv = device;
+ mtd_set_dev_defaults(mtd);
+ lockdep_set_class(&mtd->master.partitions_lock, &concat_partitions_lock_key);
+ mtd_set_release(mtd, mtd_virt_concat_release);
+ device->mtd = mtd;
+ ret = add_mtd_device(mtd);
+ if (ret)
+ mtd_put_master(mtd);
+ else
+ item->device = device;
+ kfree(parts);
return ret;
-destroy_items:
- mtd_virt_concat_destroy_items();
-
+out_device:
+ kfree(device);
+out_name:
+ kfree(name);
+out_parts:
+ kfree(parts);
return ret;
}
-/**
- * mtd_virt_concat_create_join - Create and register the concatenated
- * MTD device.
- *
- * Return: 0 on success, -error otherwise.
- */
-int mtd_virt_concat_create_join(void)
+int mtd_virt_concat_create_join(struct mtd_info *master)
{
struct mtd_virt_concat_node *item;
- struct mtd_concat *concat;
- struct mtd_info *mtd;
- ssize_t name_sz;
- int ret, idx;
- char *name;
-
- list_for_each_entry(item, &concat_node_list, head) {
- concat = item->concat;
- /*
- * Check if item->count != concat->num_subdev, it indicates
- * that the MTD information for all devices included in the
- * concatenation are not handy, concat MTD device can't be
- * created hence switch to next concat device.
- */
- if (item->count != concat->num_subdev) {
- continue;
- } else {
- /* Calculate the legth of the name of the virtual device */
- for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++)
- name_sz += (strlen(concat->subdev[idx]->name) + 1);
- name_sz += strlen(CONCAT_POSTFIX);
- name = kmalloc(name_sz + 1, GFP_KERNEL);
- if (!name) {
- mtd_virt_concat_put_mtd_devices(concat);
- return -ENOMEM;
- }
+ unsigned int i;
+ int ret;
- ret = 0;
- for (idx = 0; idx < concat->num_subdev; idx++) {
- ret += sprintf((name + ret), "%s-",
- concat->subdev[idx]->name);
- }
- sprintf((name + ret), CONCAT_POSTFIX);
-
- if (concat->mtd.name) {
- ret = memcmp(concat->mtd.name, name, name_sz);
- if (ret == 0) {
- kfree(name);
- continue;
- }
- }
- mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name);
- if (!mtd) {
- kfree(name);
- return -ENXIO;
+ guard(mutex)(&concat_lock);
+ list_for_each_entry(item, &concat_node_list, node) {
+ bool ready = true, includes_master = false;
+
+ for (i = 0; i < item->count; i++) {
+ struct mtd_virt_concat_part *part = &item->parts[i];
+
+ if (part->mtd && mtd_get_master(part->mtd) == master) {
+ part->ready = true;
+ includes_master = true;
}
- concat->mtd = *mtd;
- /* Arbitrary set the first device as parent */
- concat->mtd.dev.parent = concat->subdev[0]->dev.parent;
- concat->mtd.dev = concat->subdev[0]->dev;
-
- /* Add the mtd device */
- ret = add_mtd_device(&concat->mtd);
- if (ret)
- goto destroy_concat;
+ ready &= part->ready;
}
+ if (!ready || !includes_master || item->device)
+ continue;
+ ret = mtd_virt_concat_register(item);
+ if (ret)
+ return ret;
}
-
return 0;
-
-destroy_concat:
- mtd_concat_destroy(mtd);
-
- return ret;
}
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 1c31e2a5fbe0..63feb4c9b8b0 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -111,13 +111,13 @@ static void mtd_master_release(struct kref *ref)
module_put(owner);
}
-static void mtd_get_lifetime(struct mtd_info *master)
+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)
+void mtd_put_lifetime(struct mtd_info *master)
{
if (master->master.release)
kref_put(&master->master.life_refcnt, mtd_master_release);
@@ -1016,7 +1016,7 @@ int del_mtd_device(struct mtd_info *mtd)
* Set a few defaults based on the parent devices, if not provided by the
* driver
*/
-static void mtd_set_dev_defaults(struct mtd_info *mtd)
+void mtd_set_dev_defaults(struct mtd_info *mtd)
{
if (mtd->dev.parent) {
if (!mtd->owner && mtd->dev.parent->driver)
@@ -1289,7 +1289,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
goto out;
if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
- ret = mtd_virt_concat_create_join();
+ ret = mtd_virt_concat_create_join(mtd);
if (ret < 0)
goto out;
}
@@ -1310,6 +1310,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
out:
if (ret) {
+ del_mtd_partitions(mtd);
nvmem_unregister(mtd->otp_user_nvmem);
nvmem_unregister(mtd->otp_factory_nvmem);
}
diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h
index b014861a06a6..232cceeb5742 100644
--- a/drivers/mtd/mtdcore.h
+++ b/drivers/mtd/mtdcore.h
@@ -10,6 +10,9 @@ extern struct backing_dev_info *mtd_bdi;
struct mtd_info *__mtd_next_device(int i);
int __must_check add_mtd_device(struct mtd_info *mtd);
int del_mtd_device(struct mtd_info *mtd);
+void mtd_set_dev_defaults(struct mtd_info *mtd);
+void mtd_get_lifetime(struct mtd_info *master);
+void mtd_put_lifetime(struct mtd_info *master);
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
int del_mtd_partitions(struct mtd_info *);
void release_mtd_partition(struct mtd_info *mtd);
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index c316cfe92c7e..bcbe8772d464 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -323,6 +323,9 @@ static int __mtd_del_partition(struct mtd_info *mtd)
struct mtd_info *child, *next;
int err;
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT))
+ mtd_virt_concat_destroy(mtd);
+
list_for_each_entry_safe(child, next, &mtd->partitions, part.node) {
err = __mtd_del_partition(child);
if (err)
@@ -374,6 +377,8 @@ int del_mtd_partitions(struct mtd_info *mtd)
pr_info("Deleting MTD partitions on \"%s\":\n", mtd->name);
mutex_lock(&master->master.partitions_lock);
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT))
+ mtd_virt_concat_destroy(mtd);
ret = __del_mtd_partitions(mtd);
mutex_unlock(&master->master.partitions_lock);
@@ -426,8 +431,15 @@ int add_mtd_partitions(struct mtd_info *parent,
}
if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
- if (mtd_virt_concat_add(child))
+ ret = mtd_virt_concat_add(child);
+ if (ret < 0) {
+ free_partition(child);
+ goto err_del_partitions;
+ }
+ if (ret) {
+ cur_offset = child->part.offset + child->part.size;
continue;
+ }
}
mutex_lock(&master->master.partitions_lock);
diff --git a/include/linux/mtd/concat.h b/include/linux/mtd/concat.h
index f8d4d6ac1fc1..0ba048688b15 100644
--- a/include/linux/mtd/concat.h
+++ b/include/linux/mtd/concat.h
@@ -43,23 +43,23 @@ int mtd_virt_concat_node_create(void);
* mtd_virt_concat_add - add mtd_info object to the list of subdevices for concatenation
* @mtd: pointer to new MTD device info structure
*
- * Returns true if the mtd_info object is added successfully else returns false.
+ * Returns 1 if added, 0 if not a component, or a negative error code.
*
* The mtd_info object is added to the list of subdevices for concatenation.
- * It returns true if a match is found, and false if all subdevices have
- * already been added or if the mtd_info object does not match any of the
- * intended MTD devices.
+ * A duplicate component is rejected; the caller retains ownership on error
+ * or when the partition does not match a concatenation.
*/
-bool mtd_virt_concat_add(struct mtd_info *mtd);
+int mtd_virt_concat_add(struct mtd_info *mtd);
/**
* mtd_virt_concat_create_join - Create and register the concatenated MTD device
+ * @master: master whose partition registration has completed
*
* Returns 0 on succes, or a negative error code.
*
* Creates and registers the concatenated MTD device
*/
-int mtd_virt_concat_create_join(void);
+int mtd_virt_concat_create_join(struct mtd_info *master);
/**
* mtd_virt_concat_destroy - Remove the concat that includes a specific mtd device
@@ -68,9 +68,9 @@ int mtd_virt_concat_create_join(void);
*
* Returns 0 on succes, or a negative error code.
*
- * If the mtd_info object is part of a concatenated device, all other MTD devices
- * within that concat are registered individually. The concatenated device is then
- * removed, along with its concatenation component.
+ * Unregister concatenations containing this device or its partitions and
+ * release its staged components. Components of other masters remain staged
+ * for a later rebind. Acquired handles retain their original component set.
*
*/
int mtd_virt_concat_destroy(struct mtd_info *mtd);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 05/58] mtd: rawnand: separate hardware disconnection from cleanup
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (3 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 04/58] mtd: retain virtual concatenations across component removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 06/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
` (52 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Arseniy Krasnov, Tze Yee Ng
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 also succeeds without
accessing hardware, so a retained disconnected device does not abort
system suspend; resume is a no-op.
Propagate lock-acquisition errors through Macronix OTP access and BBT
error recovery. Stop table updates on -ENODEV rather than treating
disconnection as a failed eraseblock and issuing another marker write.
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.
Cc: Arseniy Krasnov <AVKrasnov@sberdevices.ru>
Cc: Tze Yee Ng <tze.yee.ng@altera.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/internals.h | 2 +-
drivers/mtd/nand/raw/nand_base.c | 76 ++++++++++++++++++++++++++++++------
drivers/mtd/nand/raw/nand_bbt.c | 24 +++++++++---
drivers/mtd/nand/raw/nand_macronix.c | 4 +-
include/linux/mtd/rawnand.h | 9 +++--
5 files changed, 91 insertions(+), 24 deletions(-)
diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h
index 2668f527e637..dcd9536da449 100644
--- a/drivers/mtd/nand/raw/internals.h
+++ b/drivers/mtd/nand/raw/internals.h
@@ -81,7 +81,7 @@ extern const struct nand_manufacturer_ops toshiba_nand_manuf_ops;
extern const struct mtd_pairing_scheme dist3_pairing_scheme;
/* Core functions */
-void nand_get_device(struct nand_chip *chip);
+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);
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index f125c529c62d..c577ae965b18 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
*/
-void nand_get_device(struct nand_chip *chip)
+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 0;
+ }
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/drivers/mtd/nand/raw/nand_bbt.c b/drivers/mtd/nand/raw/nand_bbt.c
index 51fe991358d6..1d71a65bbd01 100644
--- a/drivers/mtd/nand/raw/nand_bbt.c
+++ b/drivers/mtd/nand/raw/nand_bbt.c
@@ -713,14 +713,17 @@ static int get_bbt_block(struct nand_chip *this, struct nand_bbt_descr *td,
* block as bad using a bad block marker and invalidating the associated
* td->pages[] entry.
*/
-static void mark_bbt_block_bad(struct nand_chip *this,
- struct nand_bbt_descr *td,
- int chip, int block)
+static int mark_bbt_block_bad(struct nand_chip *this,
+ struct nand_bbt_descr *td,
+ int chip, int block)
{
loff_t to;
int res;
- nand_get_device(this);
+ res = nand_get_device(this);
+ if (res)
+ return res;
+
bbt_mark_entry(this, block, BBT_BLOCK_WORN);
to = (loff_t)block << this->bbt_erase_shift;
@@ -731,6 +734,7 @@ static void mark_bbt_block_bad(struct nand_chip *this,
res, block);
td->pages[chip] = -1;
+ return 0;
}
/**
@@ -892,9 +896,13 @@ static int write_bbt(struct nand_chip *this, uint8_t *buf,
einfo.len = 1 << this->bbt_erase_shift;
res = nand_erase_nand(this, &einfo, 1);
if (res < 0) {
+ if (res == -ENODEV)
+ goto outerr;
pr_warn("nand_bbt: error while erasing BBT block %d\n",
res);
- mark_bbt_block_bad(this, td, chip, block);
+ res = mark_bbt_block_bad(this, td, chip, block);
+ if (res)
+ goto outerr;
continue;
}
@@ -902,9 +910,13 @@ static int write_bbt(struct nand_chip *this, uint8_t *buf,
td->options & NAND_BBT_NO_OOB ?
NULL : &buf[len]);
if (res < 0) {
+ if (res == -ENODEV)
+ goto outerr;
pr_warn("nand_bbt: error while writing BBT block %d\n",
res);
- mark_bbt_block_bad(this, td, chip, block);
+ res = mark_bbt_block_bad(this, td, chip, block);
+ if (res)
+ goto outerr;
continue;
}
diff --git a/drivers/mtd/nand/raw/nand_macronix.c b/drivers/mtd/nand/raw/nand_macronix.c
index 4fe7036ae992..1ffe2fc4943e 100644
--- a/drivers/mtd/nand/raw/nand_macronix.c
+++ b/drivers/mtd/nand/raw/nand_macronix.c
@@ -380,7 +380,9 @@ static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd,
int ret;
nand = mtd_to_nand(mtd);
- nand_get_device(nand);
+ ret = nand_get_device(nand);
+ if (ret)
+ return ret;
nand_select_target(nand, 0);
ret = macronix_30lfxg18ac_otp_enable(nand);
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 06/58] mtd: rawnand: share retained chip and controller ownership
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (4 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 05/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
` (51 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Tze Yee Ng
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.
Cc: Tze Yee Ng <tze.yee.ng@altera.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (5 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 06/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
` (50 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Boris Brezillon,
Brian Norris, Jernej Skrabec, linux-arm-kernel, linux-sunxi,
Samuel Holland, Chen-Yu Tsai
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")
Cc: Boris Brezillon <bbrezillon@kernel.org>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Jernej Skrabec <jernej.skrabec@gmail.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-sunxi@lists.linux.dev
Cc: Samuel Holland <samuel@sholland.org>
Cc: Chen-Yu Tsai <wens@kernel.org>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (6 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 09/58] mtd: rawnand: gpio: " James Hilliard
` (49 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, David Woodhouse,
Thomas Gleixner, Vitaly Wool
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")
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Thomas Gleixner <tglx@kernel.org>
Cc: Vitaly Wool <vitalywool@gmail.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 09/58] mtd: rawnand: gpio: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (7 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
` (48 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, David Woodhouse,
Russell King, Mike Rapoport
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")
Cc: David Woodhouse <David.Woodhouse@intel.com>
Cc: Russell King <rmk+kernel@arm.linux.org.uk>
Cc: Mike Rapoport <rppt@kernel.org>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 10/58] mtd: rawnand: stm32_fmc2: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (8 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 09/58] mtd: rawnand: gpio: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
` (47 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Alexandre Torgue,
Boris Brezillon, Christophe Kerello, linux-arm-kernel,
Maxime Coquelin
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")
Cc: Alexandre Torgue <alexandre.torgue@foss.st.com>
Cc: Boris Brezillon <bbrezillon@kernel.org>
Cc: Christophe Kerello <christophe.kerello@st.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 11/58] mtd: rawnand: denali: retain chips across controller removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (9 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 12/58] mtd: rawnand: arasan: " James Hilliard
` (46 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, David Woodhouse, Jason Roberts
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")
Cc: David Woodhouse <David.Woodhouse@intel.com>
Cc: Jason Roberts <jason.e.roberts@intel.com>
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..e75754bd6d61 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,6 @@ 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;
u32 bank;
int nsels, i, ret;
@@ -89,8 +89,8 @@ 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);
+ struct denali_chip *dchip __free(kfree) =
+ 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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 12/58] mtd: rawnand: arasan: retain chips across controller removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (10 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
` (45 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Boris Brezillon, Michal Simek
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")
Cc: Boris Brezillon <boris.brezillon@collabora.com>
Cc: Michal Simek <michal.simek@amd.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 13/58] mtd: rawnand: mxic: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (11 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 12/58] mtd: rawnand: arasan: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 14/58] mtd: rawnand: intel: " James Hilliard
` (44 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Mason Yang
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")
Cc: Mason Yang <masonccyang@mxic.com.tw>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 14/58] mtd: rawnand: intel: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (12 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 15/58] mtd: rawnand: tegra: " James Hilliard
` (43 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Ramuthevar Vadivel Murugan
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")
Cc: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/intel-nand-controller.c | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c
index 97c82e9c1af2..19c54d3fc260 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,16 @@ static int ebu_nand_probe(struct platform_device *pdev)
if (ret)
goto err_disable_nand;
- 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);
+ ebu_nand_disable(nand);
+ mtd_put_master(mtd);
+ goto err_cleanup_dma;
+ }
return 0;
@@ -717,9 +731,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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 15/58] mtd: rawnand: tegra: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (13 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 14/58] mtd: rawnand: intel: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
` (42 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Boris Brezillon,
Lucas Stach, Dmitry Osipenko, Jonathan Hunter, linux-tegra,
Stefan Agner, Thierry Reding
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.
Release the probe-time child-node reference on every exit. Lifetime
ownership takes its own reference before publication.
Fixes: d7d9f8ec77fe ("mtd: rawnand: add NVIDIA Tegra NAND Flash controller driver")
Cc: Boris Brezillon <bbrezillon@kernel.org>
Cc: Lucas Stach <dev@lynxeye.de>
Cc: Dmitry Osipenko <digetx@gmail.com>
Cc: Jonathan Hunter <jonathanh@nvidia.com>
Cc: linux-tegra@vger.kernel.org
Cc: Stefan Agner <stefan@agner.ch>
Cc: Thierry Reding <thierry.reding@kernel.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/tegra_nand.c | 39 +++++++++++++++++++++++++++++++--------
1 file changed, 31 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
index 1104d75b6cbe..5130bee46d60 100644
--- a/drivers/mtd/nand/raw/tegra_nand.c
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -1064,11 +1064,16 @@ 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)
{
struct device_node *np = dev->of_node;
- struct device_node *np_nand;
+ struct device_node *np_nand __free(device_node) = NULL;
int nsels, nchips = of_get_child_count(np);
struct tegra_nand_chip *nand;
struct mtd_info *mtd;
@@ -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);
@@ -1235,13 +1257,14 @@ static void tegra_nand_remove(struct platform_device *pdev)
WARN_ON(mtd_device_unregister(mtd));
- nand_cleanup(chip);
+ nand_disconnect(chip);
writel_relaxed(0, ctrl->regs + IER);
devm_free_irq(ctrl->dev, ctrl->irq, ctrl);
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 16/58] mtd: rawnand: mtk: retain chips across controller removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (14 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 15/58] mtd: rawnand: tegra: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 17/58] mtd: rawnand: rockchip: " James Hilliard
` (41 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard,
AngeloGioacchino Del Regno, Boris Brezillon, Jorge Ramirez-Ortiz,
linux-arm-kernel, linux-mediatek, Matthias Brugger
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")
Cc: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: Boris Brezillon <bbrezillon@kernel.org>
Cc: Jorge Ramirez-Ortiz <jorge.ramirez@oss.qualcomm.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-mediatek@lists.infradead.org
Cc: Matthias Brugger <matthias.bgg@gmail.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 17/58] mtd: rawnand: rockchip: retain chips across controller removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (15 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 18/58] mtd: rawnand: atmel: " James Hilliard
` (40 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Heiko Stuebner,
linux-arm-kernel, linux-rockchip, Yifeng Zhao
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")
Cc: Heiko Stuebner <heiko@sntech.de>
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-rockchip@lists.infradead.org
Cc: Yifeng Zhao <yifeng.zhao@rock-chips.com>
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 18/58] mtd: rawnand: atmel: retain chips across controller removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (16 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 17/58] mtd: rawnand: rockchip: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
` (39 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Alexandre Belloni,
Boris Brezillon, Claudiu Beznea, linux-arm-kernel, Nicolas Ferre
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")
Cc: Alexandre Belloni <alexandre.belloni@bootlin.com>
Cc: Boris Brezillon <bbrezillon@kernel.org>
Cc: Claudiu Beznea <claudiu.beznea@tuxon.dev>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Nicolas Ferre <nicolas.ferre@microchip.com>
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 9a724de67624..ed8b703f5684 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"
@@ -1629,8 +1630,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;
}
@@ -1639,7 +1641,6 @@ static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc,
struct device_node *np,
int reg_cells)
{
- struct atmel_nand *nand;
struct gpio_desc *gpio;
int numcs, ret, i;
@@ -1650,7 +1651,8 @@ 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);
+ struct atmel_nand *nand __free(kfree) =
+ kzalloc(struct_size(nand, cs, numcs), GFP_KERNEL);
if (!nand)
return ERR_PTR(-ENOMEM);
@@ -1735,7 +1737,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
@@ -1749,6 +1756,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;
}
@@ -1757,19 +1765,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
@@ -1792,7 +1810,6 @@ 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 gpio_desc *gpio;
struct resource *res;
@@ -1800,7 +1817,8 @@ 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);
+ struct atmel_nand *nand __free(kfree) =
+ kzalloc(struct_size(nand, cs, 1), GFP_KERNEL);
if (!nand)
return -ENOMEM;
@@ -1858,7 +1876,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)
@@ -2044,7 +2062,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;
@@ -2353,6 +2370,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)
{
@@ -2360,9 +2382,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)
@@ -2432,6 +2458,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)
{
@@ -2439,9 +2470,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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 19/58] mtd: rawnand: orion: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (17 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 18/58] mtd: rawnand: atmel: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 20/58] mtd: rawnand: socrates: " James Hilliard
` (38 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Fixes: 2a1dba2931dc ("[MTD] [NAND] Marvell Orion device bus NAND controller")
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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 20/58] mtd: rawnand: socrates: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (18 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 21/58] mtd: rawnand: fsl_upm: " James Hilliard
` (37 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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..f2e82f2b0fd3 100644
--- a/drivers/mtd/nand/raw/socrates_nand.c
+++ b/drivers/mtd/nand/raw/socrates_nand.c
@@ -131,6 +131,11 @@ static const struct nand_controller_ops socrates_ops = {
.attach_chip = socrates_attach_chip,
};
+static void socrates_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct socrates_nand_host, controller));
+}
+
/*
* Probe for the NAND device.
*/
@@ -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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 21/58] mtd: rawnand: fsl_upm: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (19 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 20/58] mtd: rawnand: socrates: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 22/58] mtd: rawnand: pasemi: " James Hilliard
` (36 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 22/58] mtd: rawnand: pasemi: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (20 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 21/58] mtd: rawnand: fsl_upm: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 23/58] mtd: rawnand: ams-delta: " James Hilliard
` (35 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 23/58] mtd: rawnand: ams-delta: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (21 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 22/58] mtd: rawnand: pasemi: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 24/58] mtd: rawnand: xway: " James Hilliard
` (34 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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..e918a41875a5 100644
--- a/drivers/mtd/nand/raw/ams-delta.c
+++ b/drivers/mtd/nand/raw/ams-delta.c
@@ -230,6 +230,11 @@ static const struct nand_controller_ops gpio_nand_ops = {
.setup_interface = gpio_nand_setup_interface,
};
+static void gpio_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct gpio_nand, base));
+}
+
/*
* Main initialization routine
*/
@@ -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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 24/58] mtd: rawnand: xway: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (22 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 23/58] mtd: rawnand: ams-delta: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 25/58] mtd: rawnand: hisi504: " James Hilliard
` (33 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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..1e330aa5d56b 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>
@@ -159,6 +160,11 @@ static const struct nand_controller_ops xway_nand_ops = {
.attach_chip = xway_attach_chip,
};
+static void xway_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct xway_nand_data, controller));
+}
+
/*
* Probe for the NAND device.
*/
@@ -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
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 25/58] mtd: rawnand: hisi504: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (23 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 24/58] mtd: rawnand: xway: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 26/58] mtd: rawnand: vf610: " James Hilliard
` (32 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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 host through its embedded legacy controller.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/hisi504_nand.c | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/hisi504_nand.c b/drivers/mtd/nand/raw/hisi504_nand.c
index d97270ec1185..36427a242c2b 100644
--- a/drivers/mtd/nand/raw/hisi504_nand.c
+++ b/drivers/mtd/nand/raw/hisi504_nand.c
@@ -731,6 +731,11 @@ static const struct nand_controller_ops hisi_nfc_controller_ops = {
.attach_chip = hisi_nfc_attach_chip,
};
+static void hisi_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct hinfc_host, chip.legacy.dummy_controller));
+}
+
static int hisi_nfc_probe(struct platform_device *pdev)
{
int ret = 0, irq, max_chips = HINFC504_MAX_CHIP;
@@ -740,9 +745,14 @@ static int hisi_nfc_probe(struct platform_device *pdev)
struct mtd_info *mtd;
struct device_node *np = dev->of_node;
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ chip = &host->chip;
+ chip->controller = &chip->legacy.dummy_controller;
+ ret = devm_nand_controller_init(dev, chip->controller, hisi_nfc_release);
+ if (ret)
+ return ret;
host->dev = dev;
platform_set_drvdata(pdev, host);
@@ -788,10 +798,16 @@ static int hisi_nfc_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = nand_set_release(chip, NULL);
+ if (ret) {
+ nand_cleanup(chip);
+ return ret;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(dev, "Err MTD partition=%d\n", ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
@@ -806,7 +822,8 @@ static void hisi_nfc_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_PM_SLEEP
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 26/58] mtd: rawnand: vf610: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (24 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 25/58] mtd: rawnand: hisi504: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 27/58] mtd: rawnand: technologic: " James Hilliard
` (31 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Stefan Agner
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.
Keep managed register, IRQ and clock teardown after chip disconnection.
Cc: Stefan Agner <stefan@agner.ch>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/vf610_nfc.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index 9940681810cf..903a25c549d8 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -805,6 +805,11 @@ static const struct nand_controller_ops vf610_nfc_controller_ops = {
};
+static void vf610_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct vf610_nfc, base));
+}
+
static int vf610_nfc_probe(struct platform_device *pdev)
{
struct vf610_nfc *nfc;
@@ -813,9 +818,12 @@ static int vf610_nfc_probe(struct platform_device *pdev)
int err;
int irq;
- nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &nfc->base, vf610_nfc_release);
+ if (err)
+ return err;
nfc->dev = &pdev->dev;
chip = &nfc->chip;
@@ -873,7 +881,6 @@ static int vf610_nfc_probe(struct platform_device *pdev)
vf610_nfc_preinit_controller(nfc);
- nand_controller_init(&nfc->base);
nfc->base.ops = &vf610_nfc_controller_ops;
chip->controller = &nfc->base;
@@ -885,9 +892,15 @@ static int vf610_nfc_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, nfc);
/* Register device in MTD */
- err = mtd_device_register(mtd, NULL, 0);
+ err = nand_set_release(chip, NULL);
if (err)
goto err_cleanup_nand;
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ return err;
+ }
return 0;
err_cleanup_nand:
@@ -903,7 +916,8 @@ static void vf610_nfc_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_PM_SLEEP
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 27/58] mtd: rawnand: technologic: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (25 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 26/58] mtd: rawnand: vf610: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 28/58] mtd: rawnand: loongson: " James Hilliard
` (30 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Transfer flash-node ownership to the retained-chip helper.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/technologic-nand-controller.c | 25 ++++++++++++++++------
1 file changed, 19 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/technologic-nand-controller.c b/drivers/mtd/nand/raw/technologic-nand-controller.c
index a3294aaf43bd..a67f4d45f57b 100644
--- a/drivers/mtd/nand/raw/technologic-nand-controller.c
+++ b/drivers/mtd/nand/raw/technologic-nand-controller.c
@@ -132,6 +132,11 @@ static const struct nand_controller_ops ts72xx_nand_ops = {
.exec_op = ts72xx_nand_exec_op,
};
+static void ts72xx_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct ts72xx_nand_data, controller));
+}
+
static int ts72xx_nand_probe(struct platform_device *pdev)
{
struct ts72xx_nand_data *data;
@@ -139,11 +144,13 @@ static int ts72xx_nand_probe(struct platform_device *pdev)
struct mtd_info *mtd;
int err;
- data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
- nand_controller_init(&data->controller);
+ err = devm_nand_controller_init(&pdev->dev, &data->controller, ts72xx_nand_release);
+ if (err)
+ return err;
data->controller.ops = &ts72xx_nand_ops;
data->chip.controller = &data->controller;
@@ -175,10 +182,17 @@ static int ts72xx_nand_probe(struct platform_device *pdev)
if (err)
goto err_handle_put;
- err = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
+ err = nand_set_release(&data->chip, NULL);
if (err)
goto err_clean_nand;
+ err = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0);
+ if (err) {
+ nand_disconnect(&data->chip);
+ mtd_put_master(mtd);
+ goto err_handle_put;
+ }
+ fwnode_handle_put(child);
return 0;
err_clean_nand:
@@ -191,14 +205,13 @@ static int ts72xx_nand_probe(struct platform_device *pdev)
static void ts72xx_nand_remove(struct platform_device *pdev)
{
struct ts72xx_nand_data *data = platform_get_drvdata(pdev);
- struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev);
struct nand_chip *chip = &data->chip;
int ret;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
- fwnode_handle_put(fwnode);
+ nand_disconnect(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id ts72xx_id_table[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 28/58] mtd: rawnand: loongson: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (26 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 27/58] mtd: rawnand: technologic: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 29/58] mtd: rawnand: ma35d1: " James Hilliard
` (29 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Keguang Zhang, linux-mips
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.
Disconnect the chip before DMA resource cleanup.
Cc: Keguang Zhang <keguang.zhang@gmail.com>
Cc: linux-mips@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/loongson-nand-controller.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c
index 8490412d5be1..49938c7b9453 100644
--- a/drivers/mtd/nand/raw/loongson-nand-controller.c
+++ b/drivers/mtd/nand/raw/loongson-nand-controller.c
@@ -869,15 +869,26 @@ static int loongson_nand_chip_init(struct loongson_nand_host *host)
if (ret)
return dev_err_probe(dev, ret, "failed to scan NAND chip\n");
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(chip, NULL);
if (ret) {
nand_cleanup(chip);
+ return ret;
+ }
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return dev_err_probe(dev, ret, "failed to register MTD device\n");
}
return 0;
}
+static void loongson_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct loongson_nand_host, controller));
+}
+
static int loongson_nand_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -890,9 +901,12 @@ static int loongson_nand_probe(struct platform_device *pdev)
if (!data)
return -ENODEV;
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &host->controller, loongson_nand_release);
+ if (ret)
+ return ret;
host->reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(host->reg_base))
@@ -911,8 +925,6 @@ static int loongson_nand_probe(struct platform_device *pdev)
host->data = data;
host->controller.ops = &loongson_nand_controller_ops;
- nand_controller_init(&host->controller);
-
ret = loongson_nand_controller_init(host);
if (ret)
goto err;
@@ -938,8 +950,9 @@ static void loongson_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
loongson_nand_controller_cleanup(host);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct loongson_nand_data ls1b_nand_data = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 29/58] mtd: rawnand: ma35d1: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (27 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 28/58] mtd: rawnand: loongson: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 30/58] mtd: rawnand: pl35x: " James Hilliard
` (28 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, linux-arm-kernel,
Shan-Chun Hung, Jacky Huang
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.
Unlink each chip before dropping its initial lifetime reference.
Cc: linux-arm-kernel@lists.infradead.org
Cc: Shan-Chun Hung <schung@nuvoton.com>
Cc: Jacky Huang <ychuang3@nuvoton.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
.../mtd/nand/raw/nuvoton-ma35d1-nand-controller.c | 44 +++++++++++++++++-----
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
index f659d3a6b9ef..90bdf2635a44 100644
--- a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
+++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
@@ -849,6 +849,11 @@ static const struct nand_controller_ops ma35_nfc_ops = {
.exec_op = ma35_nfc_exec_op,
};
+static void ma35_nand_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct ma35_nand_chip, chip));
+}
+
static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
struct device_node *np)
{
@@ -866,8 +871,7 @@ static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
return -EINVAL;
}
- nvtnand = devm_kzalloc(dev, struct_size(nvtnand, sels, nsels),
- GFP_KERNEL);
+ nvtnand = kzalloc(struct_size(nvtnand, sels, nsels), GFP_KERNEL);
if (!nvtnand)
return -ENOMEM;
@@ -876,17 +880,19 @@ static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
ret = of_property_read_u32_index(np, "reg", i, &cs);
if (ret) {
dev_err(dev, "reg property failure : %d\n", ret);
- return ret;
+ goto free_chip;
}
if (cs >= MA35_MAX_NSELS) {
dev_err(dev, "invalid CS: %u\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(cs, &nand->assigned_cs)) {
dev_err(dev, "CS %u already assigned\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
nvtnand->sels[i] = cs;
@@ -905,17 +911,27 @@ static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
mtd_set_ooblayout(mtd, &ma35_ooblayout_ops);
ret = nand_scan(chip, nsels);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_release(chip, ma35_nand_chip_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&nvtnand->node, &nand->chips);
return 0;
+
+free_chip:
+ kfree(nvtnand);
+ return ret;
}
static void ma35_chips_cleanup(struct ma35_nand_info *nand)
@@ -928,8 +944,9 @@ static void ma35_chips_cleanup(struct ma35_nand_info *nand)
chip = &nvtnand->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&nvtnand->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -948,16 +965,23 @@ static int ma35_nand_chips_init(struct device *dev, struct ma35_nand_info *nand)
return 0;
}
+static void ma35_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct ma35_nand_info, controller));
+}
+
static int ma35_nand_probe(struct platform_device *pdev)
{
struct ma35_nand_info *nand;
int ret = 0;
- nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL);
+ nand = kzalloc_obj(*nand);
if (!nand)
return -ENOMEM;
- nand_controller_init(&nand->controller);
+ ret = devm_nand_controller_init(&pdev->dev, &nand->controller, ma35_nand_release);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nand->chips);
nand->controller.ops = &ma35_nfc_ops;
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 30/58] mtd: rawnand: pl35x: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (28 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 29/58] mtd: rawnand: ma35d1: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 31/58] mtd: rawnand: mxc: " James Hilliard
` (27 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Michal Simek
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.
Unwind chip ownership on registration failures as well as removal.
Cc: Michal Simek <michal.simek@amd.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/pl35x-nand-controller.c | 46 ++++++++++++++++++++++------
1 file changed, 36 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 26b61d856143..952fe5dcc84d 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -1049,6 +1049,11 @@ static int pl35x_nand_reset_state(struct pl35x_nandc *nfc)
return 0;
}
+static void pl35x_nand_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct pl35x_nand, chip));
+}
+
static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
struct device_node *np)
{
@@ -1057,22 +1062,24 @@ static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
struct mtd_info *mtd;
int cs, ret;
- plnand = devm_kzalloc(nfc->dev, sizeof(*plnand), GFP_KERNEL);
+ plnand = kzalloc_obj(*plnand);
if (!plnand)
return -ENOMEM;
ret = of_property_read_u32(np, "reg", &cs);
if (ret)
- return ret;
+ goto free_chip;
if (cs >= PL35X_NAND_MAX_CS) {
dev_err(nfc->dev, "Wrong CS %d\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(cs, &nfc->assigned_cs)) {
dev_err(nfc->dev, "Already assigned CS %d\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
plnand->cs = cs;
@@ -1089,23 +1096,34 @@ static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
"%s", PL35X_NANDC_DRIVER_NAME);
if (!mtd->name) {
dev_err(nfc->dev, "Failed to allocate mtd->name\n");
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto free_chip;
}
}
ret = nand_scan(chip, 1);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_release(chip, pl35x_nand_chip_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&plnand->node, &nfc->chips);
return ret;
+
+free_chip:
+ kfree(plnand);
+ return ret;
}
static void pl35x_nand_chips_cleanup(struct pl35x_nandc *nfc)
@@ -1118,8 +1136,9 @@ static void pl35x_nand_chips_cleanup(struct pl35x_nandc *nfc)
chip = &plnand->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&plnand->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -1146,6 +1165,11 @@ static int pl35x_nand_chips_init(struct pl35x_nandc *nfc)
return ret;
}
+static void pl35x_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct pl35x_nandc, controller));
+}
+
static int pl35x_nand_probe(struct platform_device *pdev)
{
struct device *smc_dev = pdev->dev.parent;
@@ -1153,12 +1177,14 @@ static int pl35x_nand_probe(struct platform_device *pdev)
struct pl35x_nandc *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, pl35x_nand_release);
+ if (ret)
+ return ret;
nfc->dev = &pdev->dev;
- nand_controller_init(&nfc->controller);
nfc->controller.ops = &pl35x_nandc_ops;
INIT_LIST_HEAD(&nfc->chips);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 31/58] mtd: rawnand: mxc: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (29 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 30/58] mtd: rawnand: pl35x: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 32/58] mtd: rawnand: fsmc: " James Hilliard
` (26 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Fabio Estevam, Frank Li,
imx, Pengutronix Kernel Team, linux-arm-kernel, Sascha Hauer
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 legacy controller and drain I/O before clock
disable.
Cc: Fabio Estevam <festevam@gmail.com>
Cc: Frank Li <Frank.Li@nxp.com>
Cc: imx@lists.linux.dev
Cc: Pengutronix Kernel Team <kernel@pengutronix.de>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/mxc_nand.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c
index 4d8b92e7e672..d9f1e57c1ebf 100644
--- a/drivers/mtd/nand/raw/mxc_nand.c
+++ b/drivers/mtd/nand/raw/mxc_nand.c
@@ -1686,6 +1686,11 @@ static const struct nand_controller_ops mxcnd_controller_ops = {
.exec_op = mxcnd_exec_op,
};
+static void mxcnd_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct mxc_nand_host, nand.legacy.dummy_controller));
+}
+
static int mxcnd_probe(struct platform_device *pdev)
{
struct nand_chip *this;
@@ -1694,10 +1699,14 @@ static int mxcnd_probe(struct platform_device *pdev)
int err = 0;
/* Allocate memory for MTD device structure and private data */
- host = devm_kzalloc(&pdev->dev, sizeof(struct mxc_nand_host),
- GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ this = &host->nand;
+ this->controller = &this->legacy.dummy_controller;
+ err = devm_nand_controller_init(&pdev->dev, this->controller, mxcnd_release);
+ if (err)
+ return err;
/* allocate a temporary buffer for the nand_scan_ident() */
host->data_buf = devm_kzalloc(&pdev->dev, PAGE_SIZE, GFP_KERNEL);
@@ -1796,9 +1805,15 @@ static int mxcnd_probe(struct platform_device *pdev)
goto escan;
/* Register the partitions */
- err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0);
+ err = nand_set_release(this, NULL);
if (err)
goto cleanup_nand;
+ err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0);
+ if (err) {
+ nand_disconnect(this);
+ mtd_put_master(mtd);
+ goto escan;
+ }
platform_set_drvdata(pdev, host);
@@ -1821,9 +1836,10 @@ static void mxcnd_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
if (host->clk_act)
clk_disable_unprepare(host->clk);
+ mtd_put_master(nand_to_mtd(chip));
}
static struct platform_driver mxcnd_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 32/58] mtd: rawnand: fsmc: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (30 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 31/58] mtd: rawnand: mxc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 33/58] mtd: rawnand: gpmi: " James Hilliard
` (25 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Disconnect before controller disable and DMA channel release.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsmc_nand.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c
index 0b1817cdfc5a..d9cdb03e8149 100644
--- a/drivers/mtd/nand/raw/fsmc_nand.c
+++ b/drivers/mtd/nand/raw/fsmc_nand.c
@@ -1020,6 +1020,11 @@ static void fsmc_nand_disable(struct fsmc_nand_data *host)
writel(val, host->regs_va + FSMC_PC);
}
+static void fsmc_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct fsmc_nand_data, base));
+}
+
/*
* fsmc_nand_probe - Probe function
* @pdev: platform device structure
@@ -1037,9 +1042,12 @@ static int __init fsmc_nand_probe(struct platform_device *pdev)
int i;
/* Allocate memory for the device structure (and zero it) */
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &host->base, fsmc_nand_release);
+ if (ret)
+ return ret;
nand = &host->nand;
@@ -1128,7 +1136,6 @@ static int __init fsmc_nand_probe(struct platform_device *pdev)
nand->options |= NAND_KEEP_TIMINGS;
}
- nand_controller_init(&host->base);
host->base.ops = &fsmc_nand_controller_ops;
nand->controller = &host->base;
@@ -1140,9 +1147,15 @@ static int __init fsmc_nand_probe(struct platform_device *pdev)
goto release_dma_write_chan;
mtd->name = "nand";
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(nand, NULL);
if (ret)
goto cleanup_nand;
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(nand);
+ mtd_put_master(mtd);
+ goto release_dma_write_chan;
+ }
platform_set_drvdata(pdev, host);
dev_info(&pdev->dev, "FSMC NAND driver registration successful\n");
@@ -1176,13 +1189,14 @@ static void fsmc_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
fsmc_nand_disable(host);
if (host->mode == USE_DMA_ACCESS) {
dma_release_channel(host->write_dma_chan);
dma_release_channel(host->read_dma_chan);
}
+ mtd_put_master(nand_to_mtd(chip));
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 33/58] mtd: rawnand: gpmi: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (31 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 32/58] mtd: rawnand: fsmc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 34/58] mtd: rawnand: omap2: " James Hilliard
` (24 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Han Xu, imx
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.
Keep geometry available after removal while releasing DMA and clocks.
Cc: Han Xu <han.xu@nxp.com>
Cc: imx@lists.linux.dev
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 22 ++++++++++++++++++----
1 file changed, 18 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index 527165ccc839..cda3bbdca928 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -2744,7 +2744,6 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
if (ret)
return ret;
- nand_controller_init(&this->base);
this->base.ops = &gpmi_nand_controller_ops;
chip->controller = &this->base;
@@ -2759,9 +2758,15 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
if (ret)
goto err_nand_cleanup;
- 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_out;
+ }
return 0;
err_nand_cleanup:
@@ -2782,14 +2787,22 @@ static const struct of_device_id gpmi_nand_id_table[] = {
};
MODULE_DEVICE_TABLE(of, gpmi_nand_id_table);
+static void gpmi_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct gpmi_nand_data, base));
+}
+
static int gpmi_nand_probe(struct platform_device *pdev)
{
struct gpmi_nand_data *this;
int ret;
- this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL);
+ this = kzalloc_obj(*this);
if (!this)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &this->base, gpmi_nand_release);
+ if (ret)
+ return ret;
this->devdata = of_device_get_match_data(&pdev->dev);
platform_set_drvdata(pdev, this);
@@ -2838,7 +2851,7 @@ static void gpmi_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
gpmi_free_dma_buffer(this);
release_resources(this);
pm_runtime_dont_use_autosuspend(&pdev->dev);
@@ -2846,6 +2859,7 @@ static void gpmi_nand_remove(struct platform_device *pdev)
#ifndef CONFIG_PM
gpmi_disable_clk(this);
#endif
+ mtd_put_master(nand_to_mtd(chip));
}
static int gpmi_pm_suspend(struct device *dev)
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 34/58] mtd: rawnand: omap2: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (32 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 33/58] mtd: rawnand: gpmi: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 35/58] mtd: rawnand: marvell: " James Hilliard
` (23 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Preserve the shared GPMC lock and separately retain each device's state.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/omap2.c | 45 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 37 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index 4909e62dc11e..9949d4d53299 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -142,6 +142,7 @@ static u_char bch8_vector[] = {0xf3, 0xdb, 0x14, 0x16, 0x8b, 0xd2, 0xbe, 0xcc,
static u_char bch4_vector[] = {0x00, 0x6b, 0x31, 0xdd, 0x41, 0xbc, 0x10};
struct omap_nand_info {
+ struct kref refcnt;
struct nand_chip nand;
struct platform_device *pdev;
@@ -2208,6 +2209,24 @@ static const struct nand_controller_ops omap_nand_controller_ops = {
static struct nand_controller omap_gpmc_controller;
static bool omap_gpmc_controller_initialized;
+static void omap_nand_info_release(struct kref *ref)
+{
+ kfree(container_of(ref, struct omap_nand_info, refcnt));
+}
+
+static void omap_nand_info_put(void *data)
+{
+ struct omap_nand_info *info = data;
+
+ kref_put(&info->refcnt, omap_nand_info_release);
+}
+
+static void omap_nand_release(struct nand_chip *chip)
+{
+ rawnand_sw_bch_cleanup(chip);
+ omap_nand_info_put(mtd_to_omap(nand_to_mtd(chip)));
+}
+
static int omap_nand_probe(struct platform_device *pdev)
{
struct omap_nand_info *info;
@@ -2218,10 +2237,13 @@ static int omap_nand_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
void __iomem *vaddr;
- info = devm_kzalloc(&pdev->dev, sizeof(struct omap_nand_info),
- GFP_KERNEL);
+ info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
+ kref_init(&info->refcnt);
+ err = devm_add_action_or_reset(dev, omap_nand_info_put, info);
+ if (err)
+ return err;
info->pdev = pdev;
@@ -2282,9 +2304,16 @@ static int omap_nand_probe(struct platform_device *pdev)
if (err)
goto return_error;
- err = mtd_device_register(mtd, NULL, 0);
+ err = nand_set_release(nand_chip, omap_nand_release);
if (err)
goto cleanup_nand;
+ kref_get(&info->refcnt);
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err) {
+ nand_disconnect(nand_chip);
+ mtd_put_master(mtd);
+ goto release_dma;
+ }
platform_set_drvdata(pdev, mtd);
@@ -2294,11 +2323,11 @@ static int omap_nand_probe(struct platform_device *pdev)
nand_cleanup(nand_chip);
return_error:
+ rawnand_sw_bch_cleanup(nand_chip);
+release_dma:
if (!IS_ERR_OR_NULL(info->dma))
dma_release_channel(info->dma);
- rawnand_sw_bch_cleanup(nand_chip);
-
return err;
}
@@ -2308,12 +2337,12 @@ static void omap_nand_remove(struct platform_device *pdev)
struct nand_chip *nand_chip = mtd_to_nand(mtd);
struct omap_nand_info *info = mtd_to_omap(mtd);
- rawnand_sw_bch_cleanup(nand_chip);
+ WARN_ON(mtd_device_unregister(mtd));
+ nand_disconnect(nand_chip);
if (info->dma)
dma_release_channel(info->dma);
- WARN_ON(mtd_device_unregister(mtd));
- nand_cleanup(nand_chip);
+ mtd_put_master(mtd);
}
static const struct of_device_id omap_nand_ids[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 35/58] mtd: rawnand: marvell: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (33 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 34/58] mtd: rawnand: omap2: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 36/58] mtd: rawnand: renesas: " James Hilliard
` (22 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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 layout metadata used by OOB queries and unlink before put.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/marvell_nand.c | 54 ++++++++++++++++++++++++++-----------
1 file changed, 39 insertions(+), 15 deletions(-)
diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c
index 38b7eb5b992c..f3967024411c 100644
--- a/drivers/mtd/nand/raw/marvell_nand.c
+++ b/drivers/mtd/nand/raw/marvell_nand.c
@@ -2600,6 +2600,11 @@ static const struct nand_controller_ops marvell_nand_controller_ops = {
.setup_interface = marvell_nfc_setup_interface,
};
+static void marvell_nand_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct marvell_nand_chip, chip));
+}
+
static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
struct device_node *np)
{
@@ -2630,9 +2635,7 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
}
/* Alloc the nand chip structure */
- marvell_nand = devm_kzalloc(dev,
- struct_size(marvell_nand, sels, nsels),
- GFP_KERNEL);
+ marvell_nand = kzalloc(struct_size(marvell_nand, sels, nsels), GFP_KERNEL);
if (!marvell_nand) {
dev_err(dev, "could not allocate chip structure\n");
return -ENOMEM;
@@ -2654,19 +2657,21 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
if (ret) {
dev_err(dev, "could not retrieve reg property: %d\n",
ret);
- return ret;
+ goto free_chip;
}
}
if (cs >= nfc->caps->max_cs_nb) {
dev_err(dev, "invalid reg value: %u (max CS = %d)\n",
cs, nfc->caps->max_cs_nb);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(cs, &nfc->assigned_cs)) {
dev_err(dev, "CS %d already assigned\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
/*
@@ -2688,7 +2693,8 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
marvell_nand->sels[i].ndcb0_csel = NDCB0_CSEL;
break;
default:
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
/* Retrieve RB id */
@@ -2702,14 +2708,15 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
dev_err(dev,
"could not retrieve RB property: %d\n",
ret);
- return ret;
+ goto free_chip;
}
}
if (rb >= nfc->caps->max_rb_nb) {
dev_err(dev, "invalid reg value: %u (max RB = %d)\n",
rb, nfc->caps->max_rb_nb);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
marvell_nand->sels[i].rb = rb;
@@ -2737,9 +2744,14 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
ret = nand_scan(chip, marvell_nand->nsels);
if (ret) {
dev_err(dev, "could not scan the nand chip\n");
- return ret;
+ goto free_chip;
}
+ ret = nand_set_release(chip, marvell_nand_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
if (pdata)
/* Legacy bindings support only one chip */
ret = mtd_device_register(mtd, pdata->parts, pdata->nr_parts);
@@ -2747,13 +2759,18 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
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;
}
list_add_tail(&marvell_nand->node, &nfc->chips);
return 0;
+
+free_chip:
+ kfree(marvell_nand);
+ return ret;
}
static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc)
@@ -2766,8 +2783,9 @@ static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc)
chip = &entry->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&entry->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -2932,6 +2950,11 @@ static int marvell_nfc_init(struct marvell_nfc *nfc)
return 0;
}
+static void marvell_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct marvell_nfc, controller));
+}
+
static int marvell_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -2939,13 +2962,14 @@ static int marvell_nfc_probe(struct platform_device *pdev)
int ret;
int irq;
- nfc = devm_kzalloc(&pdev->dev, sizeof(struct marvell_nfc),
- GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nfc->controller, marvell_nfc_release);
+ if (ret)
+ return ret;
nfc->dev = dev;
- nand_controller_init(&nfc->controller);
nfc->controller.ops = &marvell_nand_controller_ops;
INIT_LIST_HEAD(&nfc->chips);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 36/58] mtd: rawnand: renesas: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (34 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 35/58] mtd: rawnand: marvell: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 37/58] mtd: rawnand: meson: " James Hilliard
` (21 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, linux-renesas-soc
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.
Disconnect all chips before dropping runtime PM and transfer buffers.
Cc: linux-renesas-soc@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/renesas-nand-controller.c | 45 +++++++++++++++++++-------
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c
index 201dd62b9990..a8d9e701d7d7 100644
--- a/drivers/mtd/nand/raw/renesas-nand-controller.c
+++ b/drivers/mtd/nand/raw/renesas-nand-controller.c
@@ -1201,6 +1201,11 @@ static int rnandc_alloc_dma_buf(struct rnandc *rnandc,
return 0;
}
+static void rnandc_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct rnand_chip, chip));
+}
+
static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
{
struct rnand_chip *rnand;
@@ -1217,8 +1222,7 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
}
/* Alloc the driver's NAND chip structure */
- rnand = devm_kzalloc(rnandc->dev, struct_size(rnand, sels, nsels),
- GFP_KERNEL);
+ rnand = kzalloc(struct_size(rnand, sels, nsels), GFP_KERNEL);
if (!rnand)
return -ENOMEM;
@@ -1229,17 +1233,19 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
ret = of_property_read_u32_index(np, "reg", i, &cs);
if (ret) {
dev_err(rnandc->dev, "Incomplete reg property (%d)\n", ret);
- return ret;
+ goto free_chip;
}
if (cs >= RNANDC_CS_NUM) {
dev_err(rnandc->dev, "Invalid reg property (%d)\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(cs, &rnandc->assigned_cs)) {
dev_err(rnandc->dev, "CS %d already assigned\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
/*
@@ -1257,23 +1263,29 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
mtd->dev.parent = rnandc->dev;
if (!mtd->name) {
dev_err(rnandc->dev, "Missing MTD label\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
ret = nand_scan(chip, rnand->nsels);
if (ret) {
dev_err(rnandc->dev, "Failed to scan the NAND chip (%d)\n", ret);
- return ret;
+ goto free_chip;
}
ret = rnandc_alloc_dma_buf(rnandc, mtd);
if (ret)
goto cleanup_nand;
+ ret = nand_set_release(chip, rnandc_chip_release);
+ if (ret)
+ goto cleanup_nand;
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
dev_err(rnandc->dev, "Failed to register MTD device (%d)\n", ret);
- goto cleanup_nand;
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ return ret;
}
list_add_tail(&rnand->node, &rnandc->chips);
@@ -1282,7 +1294,8 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
cleanup_nand:
nand_cleanup(chip);
-
+free_chip:
+ kfree(rnand);
return ret;
}
@@ -1296,8 +1309,9 @@ static void rnandc_chips_cleanup(struct rnandc *rnandc)
chip = &entry->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&entry->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -1316,18 +1330,25 @@ static int rnandc_chips_init(struct rnandc *rnandc)
return 0;
}
+static void rnandc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct rnandc, controller));
+}
+
static int rnandc_probe(struct platform_device *pdev)
{
struct rnandc *rnandc;
struct clk *eclk;
int irq, ret;
- rnandc = devm_kzalloc(&pdev->dev, sizeof(*rnandc), GFP_KERNEL);
+ rnandc = kzalloc_obj(*rnandc);
if (!rnandc)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &rnandc->controller, rnandc_release);
+ if (ret)
+ return ret;
rnandc->dev = &pdev->dev;
- nand_controller_init(&rnandc->controller);
rnandc->controller.ops = &rnandc_ops;
INIT_LIST_HEAD(&rnandc->chips);
init_completion(&rnandc->complete);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 37/58] mtd: rawnand: meson: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (35 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 36/58] mtd: rawnand: renesas: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 38/58] mtd: rawnand: cadence: " James Hilliard
` (20 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Jerome Brunet,
Kevin Hilman, Liang Yang, linux-amlogic, linux-arm-kernel,
Martin Blumenstingl, Neil Armstrong
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.
The deferred detach callback only frees heap-allocated transfer buffers.
Cc: Jerome Brunet <jbrunet@baylibre.com>
Cc: Kevin Hilman <khilman@baylibre.com>
Cc: Liang Yang <liang.yang@amlogic.com>
Cc: linux-amlogic@lists.infradead.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Cc: Neil Armstrong <neil.armstrong@linaro.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/meson_nand.c | 55 +++++++++++++++++++++++++++------------
1 file changed, 39 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
index b8834aa96e81..4f33ea7a1b4d 100644
--- a/drivers/mtd/nand/raw/meson_nand.c
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -1381,6 +1381,11 @@ static const struct nand_controller_ops meson_nand_controller_ops = {
.exec_op = meson_nfc_exec_op,
};
+static void meson_nfc_nand_release(struct nand_chip *nand)
+{
+ kfree(to_meson_nand(nand));
+}
+
static int
meson_nfc_nand_chip_init(struct device *dev,
struct meson_nfc *nfc, struct device_node *np)
@@ -1398,8 +1403,7 @@ meson_nfc_nand_chip_init(struct device *dev,
return -EINVAL;
}
- meson_chip = devm_kzalloc(dev, struct_size(meson_chip, sels, nsels),
- GFP_KERNEL);
+ meson_chip = kzalloc(struct_size(meson_chip, sels, nsels), GFP_KERNEL);
if (!meson_chip)
return -ENOMEM;
@@ -1410,12 +1414,13 @@ meson_nfc_nand_chip_init(struct device *dev,
if (ret) {
dev_err(dev, "could not retrieve register property: %d\n",
ret);
- return ret;
+ goto 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 free_chip;
}
}
@@ -1434,14 +1439,16 @@ meson_nfc_nand_chip_init(struct device *dev,
if (ret == -EINVAL)
nfc->no_rb_pin = true;
else if (ret)
- return ret;
+ goto free_chip;
- if (nand_rb_val)
- return -EINVAL;
+ if (nand_rb_val) {
+ ret = -EINVAL;
+ goto free_chip;
+ }
ret = nand_scan(nand, nsels);
if (ret)
- return ret;
+ goto free_chip;
if (nand->options & NAND_IS_BOOT_MEDIUM) {
ret = of_property_read_u32(np, "amlogic,boot-pages",
@@ -1449,8 +1456,7 @@ meson_nfc_nand_chip_init(struct device *dev,
if (ret) {
dev_err(dev, "could not retrieve 'amlogic,boot-pages' property: %d",
ret);
- nand_cleanup(nand);
- return ret;
+ goto cleanup_nand;
}
ret = of_property_read_u32(np, "amlogic,boot-page-step",
@@ -1458,21 +1464,30 @@ meson_nfc_nand_chip_init(struct device *dev,
if (ret) {
dev_err(dev, "could not retrieve 'amlogic,boot-page-step' property: %d",
ret);
- nand_cleanup(nand);
- return ret;
+ goto cleanup_nand;
}
}
+ ret = nand_set_release(nand, meson_nfc_nand_release);
+ if (ret)
+ goto cleanup_nand;
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(&meson_chip->node, &nfc->chips);
return 0;
+
+cleanup_nand:
+ nand_cleanup(nand);
+free_chip:
+ kfree(meson_chip);
+ return ret;
}
static void meson_nfc_nand_chips_cleanup(struct meson_nfc *nfc)
@@ -1486,8 +1501,9 @@ static void meson_nfc_nand_chips_cleanup(struct meson_nfc *nfc)
mtd = nand_to_mtd(&meson_chip->nand);
WARN_ON(mtd_device_unregister(mtd));
- nand_cleanup(&meson_chip->nand);
+ nand_disconnect(&meson_chip->nand);
list_del(&meson_chip->node);
+ mtd_put_master(mtd);
}
}
@@ -1544,21 +1560,28 @@ static const struct of_device_id meson_nfc_id_table[] = {
};
MODULE_DEVICE_TABLE(of, meson_nfc_id_table);
+static void meson_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct meson_nfc, controller));
+}
+
static int meson_nfc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct meson_nfc *nfc;
int ret, irq;
- nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL);
+ nfc = kzalloc_obj(*nfc);
if (!nfc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nfc->controller, meson_nfc_release);
+ if (ret)
+ return ret;
nfc->data = of_device_get_match_data(&pdev->dev);
if (!nfc->data)
return -ENODEV;
- nand_controller_init(&nfc->controller);
INIT_LIST_HEAD(&nfc->chips);
init_completion(&nfc->completion);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 38/58] mtd: rawnand: cadence: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (36 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 37/58] mtd: rawnand: meson: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 39/58] mtd: rawnand: qcom: " James Hilliard
` (19 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Allocate the final shared buffer before publishing any chip.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cadence-nand-controller.c | 60 +++++++++++++++++++-------
1 file changed, 44 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c
index d53b35a8b3cb..65c4eab600af 100644
--- a/drivers/mtd/nand/raw/cadence-nand-controller.c
+++ b/drivers/mtd/nand/raw/cadence-nand-controller.c
@@ -562,6 +562,7 @@ struct cdns_nand_chip {
struct cadence_nand_timings timings;
struct nand_chip chip;
u8 nsels;
+ bool registered;
struct list_head node;
/*
@@ -2991,6 +2992,11 @@ static const struct nand_controller_ops cadence_nand_controller_ops = {
.setup_interface = cadence_nand_setup_interface,
};
+static void cadence_nand_chip_release(struct nand_chip *chip)
+{
+ kfree(to_cdns_nand_chip(chip));
+}
+
static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl,
struct device_node *np)
{
@@ -3007,9 +3013,7 @@ static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl,
}
/* Allocate the nand chip structure. */
- cdns_chip = devm_kzalloc(cdns_ctrl->dev, sizeof(*cdns_chip) +
- (nsels * sizeof(u8)),
- GFP_KERNEL);
+ cdns_chip = kzalloc(struct_size(cdns_chip, cs, nsels), GFP_KERNEL);
if (!cdns_chip) {
dev_err(cdns_ctrl->dev, "could not allocate chip structure\n");
return -ENOMEM;
@@ -3024,20 +3028,22 @@ static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl,
dev_err(cdns_ctrl->dev,
"could not retrieve reg property: %d\n",
ret);
- return ret;
+ goto free_chip;
}
if (cs >= cdns_ctrl->caps2.max_banks) {
dev_err(cdns_ctrl->dev,
"invalid reg value: %u (max CS = %d)\n",
cs, cdns_ctrl->caps2.max_banks);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
if (test_and_set_bit(cs, &cdns_ctrl->assigned_cs)) {
dev_err(cdns_ctrl->dev,
"CS %d already assigned\n", cs);
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_chip;
}
cdns_chip->cs[i] = cs;
@@ -3059,20 +3065,22 @@ static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl,
ret = nand_scan(chip, cdns_chip->nsels);
if (ret) {
dev_err(cdns_ctrl->dev, "could not scan the nand chip\n");
- return ret;
+ goto free_chip;
}
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(chip, cadence_nand_chip_release);
if (ret) {
- dev_err(cdns_ctrl->dev,
- "failed to register mtd device: %d\n", ret);
nand_cleanup(chip);
- return ret;
+ goto free_chip;
}
list_add_tail(&cdns_chip->node, &cdns_ctrl->chips);
return 0;
+
+free_chip:
+ kfree(cdns_chip);
+ return ret;
}
static void cadence_nand_chips_cleanup(struct cdns_nand_ctrl *cdns_ctrl)
@@ -3083,10 +3091,13 @@ static void cadence_nand_chips_cleanup(struct cdns_nand_ctrl *cdns_ctrl)
list_for_each_entry_safe(entry, temp, &cdns_ctrl->chips, node) {
chip = &entry->chip;
- ret = mtd_device_unregister(nand_to_mtd(chip));
- WARN_ON(ret);
- nand_cleanup(chip);
+ if (entry->registered) {
+ ret = mtd_device_unregister(nand_to_mtd(chip));
+ WARN_ON(ret);
+ }
+ nand_disconnect(chip);
list_del(&entry->node);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -3127,6 +3138,7 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl)
{
dma_cap_mask_t mask;
struct dma_device *dma_dev;
+ struct cdns_nand_chip *entry;
int ret;
cdns_ctrl->cdma_desc = dma_alloc_coherent(cdns_ctrl->dev,
@@ -3181,7 +3193,6 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl)
goto dma_release_chnl;
}
- nand_controller_init(&cdns_ctrl->controller);
INIT_LIST_HEAD(&cdns_ctrl->chips);
cdns_ctrl->controller.ops = &cadence_nand_controller_ops;
@@ -3201,9 +3212,17 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl)
goto unmap_dma_resource;
}
+ list_for_each_entry(entry, &cdns_ctrl->chips, node) {
+ ret = mtd_device_register(nand_to_mtd(&entry->chip), NULL, 0);
+ if (ret)
+ goto unmap_dma_resource;
+ entry->registered = true;
+ }
+
return 0;
unmap_dma_resource:
+ cadence_nand_chips_cleanup(cdns_ctrl);
dma_unmap_resource(dma_dev->dev, cdns_ctrl->io.iova_dma,
cdns_ctrl->io.size, DMA_BIDIRECTIONAL, 0);
@@ -3260,6 +3279,11 @@ static const struct of_device_id cadence_nand_dt_ids[] = {
MODULE_DEVICE_TABLE(of, cadence_nand_dt_ids);
+static void cadence_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct cadence_nand_dt, cdns_ctrl.controller));
+}
+
static int cadence_nand_dt_probe(struct platform_device *ofdev)
{
struct resource *res;
@@ -3275,11 +3299,15 @@ static int cadence_nand_dt_probe(struct platform_device *ofdev)
return -ENOMEM;
}
- dt = devm_kzalloc(&ofdev->dev, sizeof(*dt), GFP_KERNEL);
+ dt = kzalloc_obj(*dt);
if (!dt)
return -ENOMEM;
cdns_ctrl = &dt->cdns_ctrl;
+ ret = devm_nand_controller_init(&ofdev->dev, &cdns_ctrl->controller,
+ cadence_nand_release);
+ if (ret)
+ return ret;
cdns_ctrl->caps1 = devdata;
cdns_ctrl->dev = &ofdev->dev;
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 39/58] mtd: rawnand: qcom: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (37 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 38/58] mtd: rawnand: cadence: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 40/58] mtd: rawnand: cafe: " James Hilliard
` (18 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, linux-arm-msm,
Manivannan Sadhasivam
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.
Install lifetime ownership after scanning each chip and before any MTD
device is published. On a partial probe failure, unregister only the
published chips, then disconnect and release every initialized chip.
Cc: linux-arm-msm@vger.kernel.org
Cc: Manivannan Sadhasivam <mani@kernel.org>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/qcom_nandc.c | 46 +++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index 978fa84f051d..2e8fc574f718 100644
--- a/drivers/mtd/nand/raw/qcom_nandc.c
+++ b/drivers/mtd/nand/raw/qcom_nandc.c
@@ -1903,7 +1903,6 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
{
u32 nand_ctrl;
- nand_controller_init(&nandc->controller);
nandc->controller.ops = &qcom_nandc_ops;
/* kill onenand */
@@ -2010,6 +2009,11 @@ static int qcom_nand_host_parse_boot_partitions(struct qcom_nand_controller *nan
return 0;
}
+static void qcom_nand_host_release(struct nand_chip *chip)
+{
+ kfree(to_qcom_nand_host(chip));
+}
+
static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
struct qcom_nand_host *host,
struct device_node *dn)
@@ -2022,13 +2026,16 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
ret = of_property_read_u32(dn, "reg", &host->cs);
if (ret) {
dev_err(dev, "can't get chip-select\n");
- return -ENXIO;
+ ret = -ENXIO;
+ goto free_host;
}
nand_set_flash_node(chip, dn);
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "qcom_nand.%d", host->cs);
- if (!mtd->name)
- return -ENOMEM;
+ if (!mtd->name) {
+ ret = -ENOMEM;
+ goto free_host;
+ }
mtd->owner = THIS_MODULE;
mtd->dev.parent = dev;
@@ -2053,7 +2060,7 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
ret = nand_scan(chip, 1);
if (ret)
- return ret;
+ goto free_host;
if (nandc->props->use_codeword_fixup) {
ret = qcom_nand_host_parse_boot_partitions(nandc, host, dn);
@@ -2061,10 +2068,15 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
goto err;
}
+ ret = nand_set_release(chip, qcom_nand_host_release);
+ if (ret)
+ goto err;
return 0;
err:
nand_cleanup(chip);
+free_host:
+ kfree(host);
return ret;
}
@@ -2076,15 +2088,13 @@ static int qcom_probe_nand_devices(struct qcom_nand_controller *nandc)
int ret = -ENODEV;
for_each_available_child_of_node_scoped(dn, child) {
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
ret = qcom_nand_host_init(nandc, host, child);
- if (ret) {
- devm_kfree(dev, host);
+ if (ret)
return ret;
- }
list_add_tail(&host->node, &nandc->host_list);
}
@@ -2106,12 +2116,16 @@ static void qcom_nandc_cleanup_chips(struct qcom_nand_controller *nandc)
int ret;
list_for_each_entry_safe(host, tmp, &nandc->host_list, node) {
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
if (host->registered) {
- ret = mtd_device_unregister(nand_to_mtd(&host->chip));
+ ret = mtd_device_unregister(mtd);
WARN_ON(ret);
}
- nand_cleanup(&host->chip);
+ nand_disconnect(chip);
list_del(&host->node);
+ mtd_put_master(mtd);
}
}
@@ -2141,6 +2155,11 @@ static int qcom_nandc_parse_dt(struct platform_device *pdev)
return 0;
}
+static void qcom_nandc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct qcom_nand_controller, controller));
+}
+
static int qcom_nandc_probe(struct platform_device *pdev)
{
struct qcom_nand_controller *nandc;
@@ -2149,9 +2168,12 @@ static int qcom_nandc_probe(struct platform_device *pdev)
struct resource *res;
int ret;
- nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL);
+ nandc = kzalloc_obj(*nandc);
if (!nandc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nandc->controller, qcom_nandc_release);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, nandc);
nandc->dev = dev;
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 40/58] mtd: rawnand: cafe: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (38 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 39/58] mtd: rawnand: qcom: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 41/58] mtd: rawnand: brcmnand: " James Hilliard
` (17 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, 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.
Free DMA memory at teardown and clear its pointer before deferred
detach.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cafe_nand.c | 37 +++++++++++++++++++++++++++----------
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c
index f3117b031cd2..bcf7482410e7 100644
--- a/drivers/mtd/nand/raw/cafe_nand.c
+++ b/drivers/mtd/nand/raw/cafe_nand.c
@@ -651,7 +651,10 @@ static void cafe_nand_detach_chip(struct nand_chip *chip)
{
struct cafe_priv *cafe = nand_get_controller_data(chip);
- dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr);
+ if (cafe->dmabuf) {
+ dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr);
+ cafe->dmabuf = NULL;
+ }
}
static const struct nand_controller_ops cafe_nand_controller_ops = {
@@ -659,6 +662,11 @@ static const struct nand_controller_ops cafe_nand_controller_ops = {
.detach_chip = cafe_nand_detach_chip,
};
+static void cafe_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct cafe_priv, nand.legacy.dummy_controller));
+}
+
static int cafe_nand_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
@@ -683,6 +691,10 @@ static int cafe_nand_probe(struct pci_dev *pdev,
err = -ENOMEM;
goto out_disable_device;
}
+ cafe->nand.controller = &cafe->nand.legacy.dummy_controller;
+ err = devm_nand_controller_init(&pdev->dev, cafe->nand.controller, cafe_nand_release);
+ if (err)
+ goto out_disable_device;
mtd = nand_to_mtd(&cafe->nand);
mtd->dev.parent = &pdev->dev;
@@ -693,7 +705,7 @@ static int cafe_nand_probe(struct pci_dev *pdev,
if (!cafe->mmio) {
dev_warn(&pdev->dev, "failed to iomap\n");
err = -ENOMEM;
- goto out_free_mtd;
+ goto out_disable_device;
}
cafe->rs = init_rs_non_canonical(12, &cafe_mul, 0, 1, 8);
@@ -785,9 +797,16 @@ static int cafe_nand_probe(struct pci_dev *pdev,
pci_set_drvdata(pdev, mtd);
mtd->name = "cafe_nand";
- err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0);
+ err = nand_set_release(&cafe->nand, NULL);
if (err)
goto out_cleanup_nand;
+ err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0);
+ if (err) {
+ nand_disconnect(&cafe->nand);
+ cafe_nand_detach_chip(&cafe->nand);
+ mtd_put_master(mtd);
+ goto out_irq;
+ }
goto out;
@@ -801,8 +820,6 @@ static int cafe_nand_probe(struct pci_dev *pdev,
free_rs(cafe->rs);
out_ior:
pci_iounmap(pdev, cafe->mmio);
- out_free_mtd:
- kfree(cafe);
out_disable_device:
pci_disable_device(pdev);
out:
@@ -816,17 +833,17 @@ static void cafe_nand_remove(struct pci_dev *pdev)
struct cafe_priv *cafe = nand_get_controller_data(chip);
int ret;
+ ret = mtd_device_unregister(mtd);
+ WARN_ON(ret);
+ nand_disconnect(chip);
/* Disable NAND IRQ in global IRQ mask register */
cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK);
free_irq(pdev->irq, mtd);
- ret = mtd_device_unregister(mtd);
- WARN_ON(ret);
- nand_cleanup(chip);
+ cafe_nand_detach_chip(chip);
free_rs(cafe->rs);
pci_iounmap(pdev, cafe->mmio);
- dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr);
- kfree(cafe);
pci_disable_device(pdev);
+ mtd_put_master(mtd);
}
static const struct pci_device_id cafe_nand_tbl[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 41/58] mtd: rawnand: brcmnand: retain open-handle state on removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (39 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 40/58] mtd: rawnand: cafe: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 42/58] mtd: rawnand: au1550nd: retain chip state across removal James Hilliard
` (16 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard,
Broadcom internal kernel review list, Brian Norris, Kamal Dasu
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.
Unwind previously registered chip-selects on probe failure.
Cc: Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>
Cc: Brian Norris <computersforpeace@gmail.com>
Cc: Kamal Dasu <kamal.dasu@broadcom.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/brcmnand/brcmnand.c | 72 +++++++++++++++++++++++---------
1 file changed, 52 insertions(+), 20 deletions(-)
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index 4a1f5e736a1c..eaf0279518ca 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -3077,6 +3077,11 @@ static const struct nand_controller_ops brcmnand_controller_ops = {
.exec_op = brcmnand_exec_op,
};
+static void brcmnand_host_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct brcmnand_host, chip));
+}
+
static int brcmnand_init_cs(struct brcmnand_host *host,
const char * const *part_probe_types)
{
@@ -3093,8 +3098,10 @@ static int brcmnand_init_cs(struct brcmnand_host *host,
nand_set_controller_data(chip, host);
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d",
host->cs);
- if (!mtd->name)
- return -ENOMEM;
+ if (!mtd->name) {
+ ret = -ENOMEM;
+ goto free_host;
+ }
mtd->owner = THIS_MODULE;
mtd->dev.parent = dev;
@@ -3123,12 +3130,23 @@ static int brcmnand_init_cs(struct brcmnand_host *host,
ret = nand_scan(chip, 1);
if (ret)
- return ret;
+ goto free_host;
- ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
- if (ret)
+ ret = nand_set_release(chip, brcmnand_host_release);
+ if (ret) {
nand_cleanup(chip);
+ goto free_host;
+ }
+ ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ }
+
+ return ret;
+free_host:
+ kfree(host);
return ret;
}
@@ -3294,6 +3312,26 @@ static int brcmnand_edu_setup(struct platform_device *pdev)
return 0;
}
+static void brcmnand_chips_cleanup(struct brcmnand_controller *ctrl)
+{
+ struct brcmnand_host *host, *tmp;
+
+ list_for_each_entry_safe(host, tmp, &ctrl->host_list, node) {
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ WARN_ON(mtd_device_unregister(mtd));
+ nand_disconnect(chip);
+ list_del(&host->node);
+ mtd_put_master(mtd);
+ }
+}
+
+static void brcmnand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct brcmnand_controller, controller));
+}
+
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
{
struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
@@ -3307,9 +3345,12 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
if (dn && !of_match_node(brcmnand_of_match, dn))
return -ENODEV;
- ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
+ ctrl = kzalloc_obj(*ctrl);
if (!ctrl)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &ctrl->controller, brcmnand_release);
+ if (ret)
+ return ret;
dev_set_drvdata(dev, ctrl);
ctrl->dev = dev;
@@ -3324,7 +3365,6 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
init_completion(&ctrl->done);
init_completion(&ctrl->dma_done);
init_completion(&ctrl->edu_done);
- nand_controller_init(&ctrl->controller);
ctrl->controller.ops = &brcmnand_controller_ops;
INIT_LIST_HEAD(&ctrl->host_list);
@@ -3489,7 +3529,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
for_each_available_child_of_node_scoped(dn, child) {
if (of_device_is_compatible(child, "brcm,nandcs")) {
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host) {
ret = -ENOMEM;
goto err;
@@ -3500,7 +3540,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
ret = of_property_read_u32(child, "reg", &host->cs);
if (ret) {
dev_err(dev, "can't get chip-select\n");
- devm_kfree(dev, host);
+ kfree(host);
continue;
}
@@ -3511,7 +3551,6 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
if (ret == -EPROBE_DEFER)
goto err;
- devm_kfree(dev, host);
continue; /* Try all chip-selects */
}
@@ -3528,7 +3567,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
}
/* If we got there we must have been probing via platform data */
- host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host) {
ret = -ENOMEM;
goto err;
@@ -3554,6 +3593,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
return 0;
err:
+ brcmnand_chips_cleanup(ctrl);
clk_disable_unprepare(ctrl->clk);
return ret;
@@ -3563,16 +3603,8 @@ EXPORT_SYMBOL_GPL(brcmnand_probe);
void brcmnand_remove(struct platform_device *pdev)
{
struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
- struct brcmnand_host *host;
- struct nand_chip *chip;
- int ret;
- list_for_each_entry(host, &ctrl->host_list, node) {
- chip = &host->chip;
- ret = mtd_device_unregister(nand_to_mtd(chip));
- WARN_ON(ret);
- nand_cleanup(chip);
- }
+ brcmnand_chips_cleanup(ctrl);
clk_disable_unprepare(ctrl->clk);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 42/58] mtd: rawnand: au1550nd: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (40 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 41/58] mtd: rawnand: brcmnand: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 43/58] mtd: rawnand: sharpsl: " James Hilliard
` (15 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Drain NAND access before unmapping the chip-select window, and retain
the chip and controller until the final MTD reference is released. Check
MTD registration errors so unsuccessful publication releases the same
ownership.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/au1550nd.c | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/au1550nd.c b/drivers/mtd/nand/raw/au1550nd.c
index 6796afe9cd29..ba9019ac698e 100644
--- a/drivers/mtd/nand/raw/au1550nd.c
+++ b/drivers/mtd/nand/raw/au1550nd.c
@@ -251,6 +251,11 @@ static const struct nand_controller_ops au1550nd_ops = {
.attach_chip = au1550nd_attach_chip,
};
+static void au1550nd_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct au1550nd_ctx, controller));
+}
+
static int au1550nd_probe(struct platform_device *pdev)
{
struct au1550nd_platdata *pd;
@@ -269,6 +274,9 @@ static int au1550nd_probe(struct platform_device *pdev)
ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &ctx->controller, au1550nd_release);
+ if (ret)
+ return ret;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -302,7 +310,6 @@ static int au1550nd_probe(struct platform_device *pdev)
}
ctx->cs = cs;
- nand_controller_init(&ctx->controller);
ctx->controller.ops = &au1550nd_ops;
this->controller = &ctx->controller;
@@ -322,7 +329,17 @@ static int au1550nd_probe(struct platform_device *pdev)
goto out3;
}
- mtd_device_register(mtd, pd->parts, pd->num_parts);
+ ret = nand_set_release(this, NULL);
+ if (ret) {
+ nand_cleanup(this);
+ goto out3;
+ }
+ ret = mtd_device_register(mtd, pd->parts, pd->num_parts);
+ if (ret) {
+ nand_disconnect(this);
+ mtd_put_master(mtd);
+ goto out3;
+ }
platform_set_drvdata(pdev, ctx);
@@ -333,7 +350,6 @@ static int au1550nd_probe(struct platform_device *pdev)
out2:
release_mem_region(r->start, resource_size(r));
out1:
- kfree(ctx);
return ret;
}
@@ -346,10 +362,10 @@ static void au1550nd_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
iounmap(ctx->base);
release_mem_region(r->start, 0x1000);
- kfree(ctx);
+ mtd_put_master(nand_to_mtd(chip));
}
static struct platform_driver au1550nd_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 43/58] mtd: rawnand: sharpsl: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (41 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 42/58] mtd: rawnand: au1550nd: retain chip state across removal James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 44/58] mtd: rawnand: davinci: " James Hilliard
` (14 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Separate chip-select unmapping from chip storage reclamation.
Outstanding MTD references retain the scanned chip and layout metadata
after removal; new hardware operations fail once the chip is
disconnected.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sharpsl.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/sharpsl.c b/drivers/mtd/nand/raw/sharpsl.c
index 4154ab74f169..1ffd2b210d20 100644
--- a/drivers/mtd/nand/raw/sharpsl.c
+++ b/drivers/mtd/nand/raw/sharpsl.c
@@ -114,6 +114,11 @@ static const struct nand_controller_ops sharpsl_ops = {
.attach_chip = sharpsl_attach_chip,
};
+static void sharpsl_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct sharpsl_nand, controller));
+}
+
/*
* Main initialization routine
*/
@@ -135,6 +140,9 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
sharpsl = kzalloc_obj(struct sharpsl_nand);
if (!sharpsl)
return -ENOMEM;
+ err = devm_nand_controller_init(&pdev->dev, &sharpsl->controller, sharpsl_nand_release);
+ if (err)
+ return err;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -154,7 +162,6 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
/* Get pointer to private data */
this = (struct nand_chip *)(&sharpsl->chip);
- nand_controller_init(&sharpsl->controller);
sharpsl->controller.ops = &sharpsl_ops;
this->controller = &sharpsl->controller;
@@ -188,10 +195,16 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
/* Register the partitions */
mtd->name = "sharpsl-nand";
- err = mtd_device_parse_register(mtd, data->part_parsers, NULL,
- data->partitions, data->nr_partitions);
+ err = nand_set_release(this, NULL);
if (err)
goto err_add;
+ err = mtd_device_parse_register(mtd, data->part_parsers, NULL,
+ data->partitions, data->nr_partitions);
+ if (err) {
+ nand_disconnect(this);
+ mtd_put_master(mtd);
+ goto err_scan;
+ }
/* Return happy */
return 0;
@@ -203,7 +216,6 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
iounmap(sharpsl->io);
err_ioremap:
err_get_res:
- kfree(sharpsl);
return err;
}
@@ -221,12 +233,11 @@ static void sharpsl_nand_remove(struct platform_device *pdev)
WARN_ON(ret);
/* Release resources */
- nand_cleanup(chip);
+ nand_disconnect(chip);
iounmap(sharpsl->io);
- /* Free the driver's structure */
- kfree(sharpsl);
+ mtd_put_master(nand_to_mtd(chip));
}
static struct platform_driver sharpsl_nand_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 44/58] mtd: rawnand: davinci: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (42 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 43/58] mtd: rawnand: sharpsl: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 45/58] mtd: rawnand: sh_flctl: " James Hilliard
` (13 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Disconnect the chip before releasing the shared four-bit ECC engine.
Retain the chip and controller while MTD references remain, with final
NAND cleanup independent of the device-managed hardware resources.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/davinci_nand.c | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c
index 3986553881d0..2231e6a45114 100644
--- a/drivers/mtd/nand/raw/davinci_nand.c
+++ b/drivers/mtd/nand/raw/davinci_nand.c
@@ -895,6 +895,11 @@ static const struct nand_controller_ops davinci_nand_controller_ops = {
.setup_interface = davinci_nand_setup_interface,
};
+static void nand_davinci_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct davinci_nand_info, controller));
+}
+
static int nand_davinci_probe(struct platform_device *pdev)
{
struct davinci_nand_pdata *pdata;
@@ -919,9 +924,12 @@ static int nand_davinci_probe(struct platform_device *pdev)
if (pdata->core_chipsel > 3)
return -ENODEV;
- info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ info = kzalloc_obj(*info);
if (!info)
return -ENOMEM;
+ ret = devm_nand_controller_init(&pdev->dev, &info->controller, nand_davinci_release);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, info);
@@ -986,7 +994,6 @@ static int nand_davinci_probe(struct platform_device *pdev)
spin_unlock_irq(&davinci_nand_lock);
/* Scan to find existence of the device(s) */
- nand_controller_init(&info->controller);
info->controller.ops = &davinci_nand_controller_ops;
info->chip.controller = &info->controller;
ret = nand_scan(&info->chip, pdata->mask_chipsel ? 2 : 1);
@@ -995,12 +1002,18 @@ static int nand_davinci_probe(struct platform_device *pdev)
return ret;
}
+ ret = nand_set_release(&info->chip, NULL);
+ if (ret)
+ goto err_cleanup_nand;
if (pdata->parts)
ret = mtd_device_register(mtd, pdata->parts, pdata->nr_parts);
else
ret = mtd_device_register(mtd, NULL, 0);
- if (ret < 0)
- goto err_cleanup_nand;
+ if (ret < 0) {
+ nand_disconnect(&info->chip);
+ mtd_put_master(mtd);
+ return ret;
+ }
val = davinci_nand_readl(info, NRCSR_OFFSET);
dev_info(&pdev->dev, "controller rev. %d.%d\n",
@@ -1020,14 +1033,16 @@ static void nand_davinci_remove(struct platform_device *pdev)
struct nand_chip *chip = &info->chip;
int ret;
+ ret = mtd_device_unregister(nand_to_mtd(chip));
+ WARN_ON(ret);
+ nand_disconnect(chip);
+
spin_lock_irq(&davinci_nand_lock);
if (chip->ecc.placement == NAND_ECC_PLACEMENT_INTERLEAVED)
ecc4_busy = false;
spin_unlock_irq(&davinci_nand_lock);
- ret = mtd_device_unregister(nand_to_mtd(chip));
- WARN_ON(ret);
- nand_cleanup(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
static struct platform_driver nand_davinci_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 45/58] mtd: rawnand: sh_flctl: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (43 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 44/58] mtd: rawnand: davinci: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 46/58] mtd: rawnand: lpc32xx_slc: " James Hilliard
` (12 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Unregister and disconnect the NAND chip before releasing the DMA
channel. Retain chip and controller storage until the last MTD reference
is dropped.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sh_flctl.c | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c
index 97f733e481ff..291d809d0ea1 100644
--- a/drivers/mtd/nand/raw/sh_flctl.c
+++ b/drivers/mtd/nand/raw/sh_flctl.c
@@ -1109,6 +1109,11 @@ static struct sh_flctl_platform_data *flctl_parse_dt(struct device *dev)
return pdata;
}
+static void flctl_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct sh_flctl, chip.legacy.dummy_controller));
+}
+
static int flctl_probe(struct platform_device *pdev)
{
struct resource *res;
@@ -1119,9 +1124,14 @@ static int flctl_probe(struct platform_device *pdev)
int ret;
int irq;
- flctl = devm_kzalloc(&pdev->dev, sizeof(struct sh_flctl), GFP_KERNEL);
+ flctl = kzalloc_obj(*flctl);
if (!flctl)
return -ENOMEM;
+ nand = &flctl->chip;
+ nand->controller = &nand->legacy.dummy_controller;
+ ret = devm_nand_controller_init(&pdev->dev, nand->controller, flctl_release);
+ if (ret)
+ return ret;
flctl->reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(flctl->reg))
@@ -1187,9 +1197,15 @@ static int flctl_probe(struct platform_device *pdev)
if (ret)
goto err_chip;
- ret = mtd_device_register(flctl_mtd, pdata->parts, pdata->nr_parts);
+ ret = nand_set_release(nand, NULL);
if (ret)
goto cleanup_nand;
+ ret = mtd_device_register(flctl_mtd, pdata->parts, pdata->nr_parts);
+ if (ret) {
+ nand_disconnect(nand);
+ mtd_put_master(flctl_mtd);
+ goto err_chip;
+ }
return 0;
@@ -1207,11 +1223,12 @@ static void flctl_remove(struct platform_device *pdev)
struct nand_chip *chip = &flctl->chip;
int ret;
- flctl_release_dma(flctl);
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ flctl_release_dma(flctl);
pm_runtime_disable(&pdev->dev);
+ mtd_put_master(nand_to_mtd(chip));
}
static struct platform_driver flctl_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 46/58] mtd: rawnand: lpc32xx_slc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (44 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 45/58] mtd: rawnand: sh_flctl: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 47/58] mtd: rawnand: lpc32xx_mlc: " James Hilliard
` (11 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, linux-arm-kernel,
Piotr Wojtaszczyk, Vladimir Zapolskiy
Drain chip operations before releasing DMA and changing the controller
and write-protect signals. Retain the embedded NAND chip and controller
until the final MTD reference releases their metadata.
Cc: linux-arm-kernel@lists.infradead.org
Cc: Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/lpc32xx_slc.c | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c
index b4d90afa9267..85945e1dd25a 100644
--- a/drivers/mtd/nand/raw/lpc32xx_slc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_slc.c
@@ -817,6 +817,12 @@ static const struct nand_controller_ops lpc32xx_nand_controller_ops = {
.attach_chip = lpc32xx_nand_attach_chip,
};
+static void lpc32xx_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct lpc32xx_nand_host,
+ nand_chip.legacy.dummy_controller));
+}
+
/*
* Probe for NAND controller
*/
@@ -829,9 +835,14 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
int res;
/* Allocate memory for the device structure (and zero it) */
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ chip = &host->nand_chip;
+ chip->controller = &chip->legacy.dummy_controller;
+ res = devm_nand_controller_init(&pdev->dev, chip->controller, lpc32xx_nand_release);
+ if (res)
+ return res;
host->io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &rc);
if (IS_ERR(host->io_base))
@@ -915,10 +926,16 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
goto release_dma;
mtd->name = "nxp_lpc3220_slc";
- res = mtd_device_register(mtd, host->ncfg->parts,
- host->ncfg->num_parts);
+ res = nand_set_release(chip, NULL);
if (res)
goto cleanup_nand;
+ res = mtd_device_register(mtd, host->ncfg->parts,
+ host->ncfg->num_parts);
+ if (res) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ goto release_dma;
+ }
return 0;
@@ -944,7 +961,7 @@ static void lpc32xx_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
dma_release_channel(host->dma_chan);
/* Force CE high */
@@ -953,6 +970,7 @@ static void lpc32xx_nand_remove(struct platform_device *pdev)
writel(tmp, SLC_CTRL(host->io_base));
lpc32xx_wp_enable(host);
+ mtd_put_master(nand_to_mtd(chip));
}
static int lpc32xx_nand_resume(struct platform_device *pdev)
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 47/58] mtd: rawnand: lpc32xx_mlc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (45 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 46/58] mtd: rawnand: lpc32xx_slc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 48/58] mtd: rawnand: ndfc: " James Hilliard
` (10 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, linux-arm-kernel,
Piotr Wojtaszczyk, Vladimir Zapolskiy
Drain chip operations before releasing interrupts, DMA and controller
resources. Retain the embedded NAND chip and controller until the final
MTD reference releases their metadata.
Cc: linux-arm-kernel@lists.infradead.org
Cc: Piotr Wojtaszczyk <piotr.wojtaszczyk@timesys.com>
Cc: Vladimir Zapolskiy <vz@mleia.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/lpc32xx_mlc.c | 26 ++++++++++++++++++++++----
1 file changed, 22 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c
index 8436015edcb4..93ee8cfc0345 100644
--- a/drivers/mtd/nand/raw/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c
@@ -675,6 +675,12 @@ static const struct nand_controller_ops lpc32xx_nand_controller_ops = {
.attach_chip = lpc32xx_nand_attach_chip,
};
+static void lpc32xx_nand_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct lpc32xx_nand_host,
+ nand_chip.legacy.dummy_controller));
+}
+
/*
* Probe for NAND controller
*/
@@ -687,9 +693,14 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
int res;
/* Allocate memory for the device structure (and zero it) */
- host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL);
+ host = kzalloc_obj(*host);
if (!host)
return -ENOMEM;
+ nand_chip = &host->nand_chip;
+ nand_chip->controller = &nand_chip->legacy.dummy_controller;
+ res = devm_nand_controller_init(&pdev->dev, nand_chip->controller, lpc32xx_nand_release);
+ if (res)
+ return res;
host->pdev = pdev;
@@ -794,10 +805,16 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
mtd->name = DRV_NAME;
- res = mtd_device_register(mtd, host->ncfg->parts,
- host->ncfg->num_parts);
+ res = nand_set_release(nand_chip, NULL);
if (res)
goto cleanup_nand;
+ res = mtd_device_register(mtd, host->ncfg->parts,
+ host->ncfg->num_parts);
+ if (res) {
+ nand_disconnect(nand_chip);
+ mtd_put_master(mtd);
+ goto free_irq;
+ }
return 0;
@@ -830,7 +847,7 @@ static void lpc32xx_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
free_irq(host->irq, host);
if (use_dma)
@@ -841,6 +858,7 @@ static void lpc32xx_nand_remove(struct platform_device *pdev)
lpc32xx_wp_enable(host);
gpiod_put(host->wp_gpio);
+ mtd_put_master(nand_to_mtd(chip));
}
static int lpc32xx_nand_resume(struct platform_device *pdev)
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 48/58] mtd: rawnand: ndfc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (46 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 47/58] mtd: rawnand: lpc32xx_mlc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 49/58] mtd: rawnand: txx9ndfmc: " James Hilliard
` (9 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Allocate controller state per binding instead of reusing the static
chip-select slots. A retained MTD handle must keep its original chip
state when the platform device is rebound. Disconnect the old chip
before device resources disappear and release its state at the final MTD
put.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/ndfc.c | 37 +++++++++++++++++++++++++++----------
1 file changed, 27 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c
index d7b119028a71..50506ad87fd4 100644
--- a/drivers/mtd/nand/raw/ndfc.c
+++ b/drivers/mtd/nand/raw/ndfc.c
@@ -37,8 +37,6 @@ struct ndfc_controller {
struct nand_controller ndfc_control;
};
-static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS];
-
static void ndfc_select_chip(struct nand_chip *nchip, int chip)
{
uint32_t ccr;
@@ -173,13 +171,27 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc,
if (ret)
goto err;
+ ret = nand_set_release(chip, NULL);
+ if (ret) {
+ nand_cleanup(chip);
+ goto err;
+ }
ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ }
err:
of_node_put(flash_np);
return ret;
}
+static void ndfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct ndfc_controller, ndfc_control));
+}
+
static int ndfc_probe(struct platform_device *ofdev)
{
struct ndfc_controller *ndfc;
@@ -187,11 +199,7 @@ static int ndfc_probe(struct platform_device *ofdev)
const __be32 *reg;
u32 ccr;
u32 cs;
- int len = 0;
-
- ndfcbase = devm_platform_ioremap_resource(ofdev, 0);
- if (IS_ERR(ndfcbase))
- return PTR_ERR(ndfcbase);
+ int len = 0, ret;
/* Read the reg property to get the chip select */
reg = of_get_property(ofdev->dev.of_node, "reg", &len);
@@ -206,10 +214,18 @@ static int ndfc_probe(struct platform_device *ofdev)
return -EINVAL;
}
- ndfc = &ndfc_ctrl[cs];
+ ndfc = kzalloc_obj(*ndfc);
+ if (!ndfc)
+ return -ENOMEM;
+ ret = devm_nand_controller_init(&ofdev->dev, &ndfc->ndfc_control, ndfc_release);
+ if (ret)
+ return ret;
+
+ ndfcbase = devm_platform_ioremap_resource(ofdev, 0);
+ if (IS_ERR(ndfcbase))
+ return PTR_ERR(ndfcbase);
ndfc->chip_select = cs;
- nand_controller_init(&ndfc->ndfc_control);
ndfc->ofdev = ofdev;
dev_set_drvdata(&ofdev->dev, ndfc);
@@ -243,7 +259,8 @@ static void ndfc_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 ndfc_match[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 49/58] mtd: rawnand: txx9ndfmc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (47 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 48/58] mtd: rawnand: ndfc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 50/58] mtd: rawnand: ingenic: " James Hilliard
` (8 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Retain each chip and its shared controller while MTD references remain.
Unregister and disconnect before dropping driver ownership, and check
MTD registration failure without publishing partially initialized chip
slots.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/txx9ndfmc.c | 42 +++++++++++++++++++++++++++++++---------
1 file changed, 33 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/raw/txx9ndfmc.c b/drivers/mtd/nand/raw/txx9ndfmc.c
index 4cc6e91dbc23..9eae5002e389 100644
--- a/drivers/mtd/nand/raw/txx9ndfmc.c
+++ b/drivers/mtd/nand/raw/txx9ndfmc.c
@@ -276,17 +276,33 @@ static const struct nand_controller_ops txx9ndfmc_controller_ops = {
.attach_chip = txx9ndfmc_attach_chip,
};
+static void txx9ndfmc_chip_release(struct nand_chip *chip)
+{
+ struct txx9ndfmc_priv *priv = nand_get_controller_data(chip);
+
+ kfree(priv->mtdname);
+ kfree(priv);
+}
+
+static void txx9ndfmc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct txx9ndfmc_drvdata, controller));
+}
+
static int txx9ndfmc_probe(struct platform_device *dev)
{
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
int hold, spw;
- int i;
+ int i, ret;
struct txx9ndfmc_drvdata *drvdata;
unsigned long gbusclk = plat->gbus_clock;
- drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL);
+ drvdata = kzalloc_obj(*drvdata);
if (!drvdata)
return -ENOMEM;
+ ret = devm_nand_controller_init(&dev->dev, &drvdata->controller, txx9ndfmc_release);
+ if (ret)
+ return ret;
drvdata->base = devm_platform_ioremap_resource(dev, 0);
if (IS_ERR(drvdata->base))
return PTR_ERR(drvdata->base);
@@ -306,7 +322,6 @@ static int txx9ndfmc_probe(struct platform_device *dev)
dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n",
(gbusclk + 500000) / 1000000, hold, spw);
- nand_controller_init(&drvdata->controller);
drvdata->controller.ops = &txx9ndfmc_controller_ops;
platform_set_drvdata(dev, drvdata);
@@ -361,7 +376,18 @@ static int txx9ndfmc_probe(struct platform_device *dev)
}
mtd->name = txx9_priv->mtdname;
- mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_release(chip, txx9ndfmc_chip_release);
+ if (ret) {
+ nand_cleanup(chip);
+ txx9ndfmc_chip_release(chip);
+ continue;
+ }
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ continue;
+ }
drvdata->mtds[i] = mtd;
}
@@ -376,18 +402,16 @@ static void txx9ndfmc_remove(struct platform_device *dev)
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
struct mtd_info *mtd = drvdata->mtds[i];
struct nand_chip *chip;
- struct txx9ndfmc_priv *txx9_priv;
if (!mtd)
continue;
chip = mtd_to_nand(mtd);
- txx9_priv = nand_get_controller_data(chip);
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
- kfree(txx9_priv->mtdname);
- kfree(txx9_priv);
+ nand_disconnect(chip);
+ drvdata->mtds[i] = NULL;
+ mtd_put_master(mtd);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 50/58] mtd: rawnand: ingenic: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (48 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 49/58] mtd: rawnand: txx9ndfmc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 51/58] mtd: rawnand: mpc5121: " James Hilliard
` (7 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Harvey Hunt, linux-mips,
Paul Cercueil
Unregister and disconnect all chips before releasing the ECC provider.
Retain each chip and its shared controller until the final MTD put,
while releasing hardware resources during device removal.
Cc: Harvey Hunt <harveyhuntnexus@gmail.com>
Cc: linux-mips@vger.kernel.org
Cc: Paul Cercueil <paul@crapouillou.net>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c | 49 +++++++++++++++++++------
1 file changed, 37 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
index f1e2c82936b3..775ebf4dd3b9 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
@@ -341,6 +341,11 @@ static const struct nand_controller_ops ingenic_nand_controller_ops = {
.exec_op = ingenic_nand_exec_op,
};
+static void ingenic_nand_release(struct nand_chip *chip)
+{
+ kfree(to_ingenic_nand(nand_to_mtd(chip)));
+}
+
static int ingenic_nand_init_chip(struct platform_device *pdev,
struct ingenic_nfc *nfc,
struct device_node *np,
@@ -368,7 +373,7 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
if (IS_ERR(cs->base))
return PTR_ERR(cs->base);
- nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
+ nand = kzalloc_obj(*nand);
if (!nand)
return -ENOMEM;
@@ -377,7 +382,7 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
if (IS_ERR(nand->busy_gpio)) {
ret = PTR_ERR(nand->busy_gpio);
dev_err(dev, "failed to request busy GPIO: %d\n", ret);
- return ret;
+ goto free_chip;
}
nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW);
@@ -385,15 +390,17 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
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;
mtd = nand_to_mtd(chip);
mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev),
cs->bank);
- if (!mtd->name)
- return -ENOMEM;
+ if (!mtd->name) {
+ ret = -ENOMEM;
+ goto free_chip;
+ }
mtd->dev.parent = dev;
chip->options = NAND_NO_SUBPAGE_WRITE;
@@ -404,17 +411,27 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
chip->controller->ops = &ingenic_nand_controller_ops;
ret = nand_scan(chip, 1);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_release(chip, ingenic_nand_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
ret = mtd_device_register(mtd, NULL, 0);
if (ret) {
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
return ret;
}
list_add_tail(&nand->chip_list, &nfc->chips);
return 0;
+
+free_chip:
+ kfree(nand);
+ return ret;
}
static void ingenic_nand_cleanup_chips(struct ingenic_nfc *nfc)
@@ -429,8 +446,9 @@ static void ingenic_nand_cleanup_chips(struct ingenic_nfc *nfc)
chip = &ingenic_chip->chip;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
list_del(&ingenic_chip->chip_list);
+ mtd_put_master(nand_to_mtd(chip));
}
}
@@ -461,6 +479,11 @@ static int ingenic_nand_init_chips(struct ingenic_nfc *nfc,
return 0;
}
+static void ingenic_nfc_release(struct nand_controller *controller)
+{
+ kfree(to_ingenic_nfc(controller));
+}
+
static int ingenic_nand_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -474,9 +497,12 @@ static int ingenic_nand_probe(struct platform_device *pdev)
return -ENODEV;
}
- nfc = devm_kzalloc(dev, struct_size(nfc, cs, num_banks), GFP_KERNEL);
+ nfc = kzalloc(struct_size(nfc, cs, num_banks), GFP_KERNEL);
if (!nfc)
return -ENOMEM;
+ ret = devm_nand_controller_init(dev, &nfc->controller, ingenic_nfc_release);
+ if (ret)
+ return ret;
nfc->soc_info = device_get_match_data(dev);
if (!nfc->soc_info)
@@ -493,7 +519,6 @@ static int ingenic_nand_probe(struct platform_device *pdev)
nfc->dev = dev;
nfc->num_banks = num_banks;
- nand_controller_init(&nfc->controller);
INIT_LIST_HEAD(&nfc->chips);
ret = ingenic_nand_init_chips(nfc, pdev);
@@ -511,10 +536,10 @@ static void ingenic_nand_remove(struct platform_device *pdev)
{
struct ingenic_nfc *nfc = platform_get_drvdata(pdev);
+ ingenic_nand_cleanup_chips(nfc);
+
if (nfc->ecc)
ingenic_ecc_release(nfc->ecc);
-
- ingenic_nand_cleanup_chips(nfc);
}
static const struct jz_soc_info jz4740_soc_info = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 51/58] mtd: rawnand: mpc5121: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (49 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 50/58] mtd: rawnand: ingenic: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 52/58] mtd: rawnand: bcm47xxnflash: " James Hilliard
` (6 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Drain NAND access before unmapping the external chip-select registers.
Keep the embedded chip and controller alive until the final MTD
reference, and unwind scanned NAND state on unsupported geometry and
publication failure.
Fixes: bb315f749f8c ("mtd: nand: Add MPC5121 NAND Flash Controller driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/mpc5121_nfc.c | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c
index e6594548b7e0..1618f46f69df 100644
--- a/drivers/mtd/nand/raw/mpc5121_nfc.c
+++ b/drivers/mtd/nand/raw/mpc5121_nfc.c
@@ -611,6 +611,11 @@ static const struct nand_controller_ops mpc5121_nfc_ops = {
.attach_chip = mpc5121_nfc_attach_chip,
};
+static void mpc5121_nfc_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct mpc5121_nfc_prv, controller));
+}
+
static int mpc5121_nfc_probe(struct platform_device *op)
{
struct device_node *dn = op->dev.of_node;
@@ -636,6 +641,13 @@ static int mpc5121_nfc_probe(struct platform_device *op)
return -ENXIO;
}
+ prv = kzalloc_obj(*prv);
+ if (!prv)
+ return -ENOMEM;
+ retval = devm_nand_controller_init(dev, &prv->controller, mpc5121_nfc_release);
+ if (retval)
+ return retval;
+
regs = devm_platform_ioremap_resource(op, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
@@ -644,14 +656,9 @@ static int mpc5121_nfc_probe(struct platform_device *op)
if (irq < 0)
return irq;
- prv = devm_kzalloc(dev, sizeof(*prv), GFP_KERNEL);
- if (!prv)
- return -ENOMEM;
-
chip = &prv->chip;
mtd = nand_to_mtd(chip);
- nand_controller_init(&prv->controller);
prv->controller.ops = &mpc5121_nfc_ops;
chip->controller = &prv->controller;
@@ -782,19 +789,26 @@ static int mpc5121_nfc_probe(struct platform_device *op)
default:
dev_err(dev, "Unsupported NAND flash!\n");
retval = -ENXIO;
- goto error;
+ goto cleanup_nand;
}
dev_set_drvdata(dev, mtd);
/* Register device in MTD */
+ retval = nand_set_release(chip, NULL);
+ if (retval)
+ goto cleanup_nand;
retval = mtd_device_register(mtd, NULL, 0);
if (retval) {
dev_err(dev, "Error adding MTD device!\n");
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
goto error;
}
return 0;
+cleanup_nand:
+ nand_cleanup(chip);
error:
mpc5121_nfc_free(dev, mtd);
return retval;
@@ -808,8 +822,9 @@ static void mpc5121_nfc_remove(struct platform_device *op)
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(mtd_to_nand(mtd));
+ nand_disconnect(mtd_to_nand(mtd));
mpc5121_nfc_free(dev, mtd);
+ mtd_put_master(mtd);
}
static const struct of_device_id mpc5121_nfc_match[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 52/58] mtd: rawnand: bcm47xxnflash: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (50 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 51/58] mtd: rawnand: mpc5121: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 53/58] mtd: rawnand: fsl_elbc: " James Hilliard
` (5 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Disconnect the scanned chip on removal before the BCMA-backed hardware
state becomes unavailable. Retain the embedded NAND controller and chip
until the final MTD put, including registration failure cleanup.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/bcm47xxnflash/main.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/main.c b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
index 4d4e185c22e5..60fc440d4515 100644
--- a/drivers/mtd/nand/raw/bcm47xxnflash/main.c
+++ b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
@@ -19,6 +19,12 @@ MODULE_AUTHOR("Rafał Miłecki");
static const char *probes[] = { "bcm47xxpart", NULL };
+static void bcm47xxnflash_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct bcm47xxnflash,
+ nand_chip.legacy.dummy_controller));
+}
+
static int bcm47xxnflash_probe(struct platform_device *pdev)
{
struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev);
@@ -26,9 +32,14 @@ static int bcm47xxnflash_probe(struct platform_device *pdev)
struct mtd_info *mtd;
int err = 0;
- b47n = devm_kzalloc(&pdev->dev, sizeof(*b47n), GFP_KERNEL);
+ b47n = kzalloc_obj(*b47n);
if (!b47n)
return -ENOMEM;
+ b47n->nand_chip.controller = &b47n->nand_chip.legacy.dummy_controller;
+ err = devm_nand_controller_init(&pdev->dev, b47n->nand_chip.controller,
+ bcm47xxnflash_release);
+ if (err)
+ return err;
nand_set_controller_data(&b47n->nand_chip, b47n);
mtd = nand_to_mtd(&b47n->nand_chip);
@@ -48,9 +59,16 @@ static int bcm47xxnflash_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, b47n);
+ err = nand_set_release(&b47n->nand_chip, NULL);
+ if (err) {
+ nand_cleanup(&b47n->nand_chip);
+ return err;
+ }
err = mtd_device_parse_register(mtd, probes, NULL, NULL, 0);
if (err) {
pr_err("Failed to register MTD device: %d\n", err);
+ nand_disconnect(&b47n->nand_chip);
+ mtd_put_master(mtd);
return err;
}
@@ -65,7 +83,8 @@ static void bcm47xxnflash_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 struct platform_driver bcm47xxnflash_driver = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 53/58] mtd: rawnand: fsl_elbc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (51 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 52/58] mtd: rawnand: bcm47xxnflash: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 54/58] mtd: rawnand: fsl_ifc: " James Hilliard
` (4 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Keep a reference to the shared FCM controller for each scanned chip,
including chips retained after unbind. Count active banks separately so
a new binding cannot reuse controller state still owned by old MTD
handles.
Unregister and drain the chip before unmapping its chip-select window.
Release NAND metadata, chip storage and the shared controller reference
only after the final MTD put.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_elbc_nand.c | 68 ++++++++++++++++++++++++------------
1 file changed, 45 insertions(+), 23 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c
index c655d27bc4cc..c27e89050270 100644
--- a/drivers/mtd/nand/raw/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c
@@ -48,6 +48,7 @@ struct fsl_elbc_mtd {
struct fsl_elbc_fcm_ctrl {
struct nand_controller controller;
+ struct kref refcnt;
struct fsl_elbc_mtd *chips[MAX_BANKS];
u8 __iomem *addr; /* Address of assigned FCM buffer */
@@ -837,23 +838,35 @@ static const struct nand_controller_ops fsl_elbc_controller_ops = {
.attach_chip = fsl_elbc_attach_chip,
};
-static int fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv)
+static void fsl_elbc_controller_release(struct kref *ref)
{
- struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
- struct mtd_info *mtd = nand_to_mtd(&priv->chip);
+ kfree(container_of(ref, struct fsl_elbc_fcm_ctrl, refcnt));
+}
+
+static void fsl_elbc_chip_release(struct nand_chip *chip)
+{
+ struct fsl_elbc_fcm_ctrl *ctrl;
- kfree(mtd->name);
+ ctrl = container_of(chip->controller, struct fsl_elbc_fcm_ctrl, controller);
+ kfree(nand_get_controller_data(chip));
+ kref_put(&ctrl->refcnt, fsl_elbc_controller_release);
+}
+
+static DEFINE_MUTEX(fsl_elbc_nand_mutex);
+
+static void fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv)
+{
+ struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand;
if (priv->vbase)
iounmap(priv->vbase);
+ guard(mutex)(&fsl_elbc_nand_mutex);
elbc_fcm_ctrl->chips[priv->bank] = NULL;
- kfree(priv);
- return 0;
+ if (!--elbc_fcm_ctrl->counter)
+ priv->ctrl->nand = NULL;
}
-static DEFINE_MUTEX(fsl_elbc_nand_mutex);
-
static int fsl_elbc_nand_probe(struct platform_device *pdev)
{
struct fsl_lbc_regs __iomem *lbc;
@@ -867,6 +880,7 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
struct device *dev;
struct device_node *node = pdev->dev.of_node;
struct mtd_info *mtd;
+ const char *name;
if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs)
return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "lbc_ctrl_dev missing\n");
@@ -904,21 +918,25 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
elbc_fcm_ctrl = kzalloc_obj(*elbc_fcm_ctrl);
if (!elbc_fcm_ctrl) {
mutex_unlock(&fsl_elbc_nand_mutex);
- ret = -ENOMEM;
- goto err;
+ kfree(priv);
+ return -ENOMEM;
}
- elbc_fcm_ctrl->counter++;
+ kref_init(&elbc_fcm_ctrl->refcnt);
nand_controller_init(&elbc_fcm_ctrl->controller);
fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl;
} else {
elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
+ kref_get(&elbc_fcm_ctrl->refcnt);
}
+ elbc_fcm_ctrl->counter++;
mutex_unlock(&fsl_elbc_nand_mutex);
elbc_fcm_ctrl->chips[bank] = priv;
priv->bank = bank;
priv->ctrl = fsl_lbc_ctrl_dev;
+ priv->chip.controller = &elbc_fcm_ctrl->controller;
+ nand_set_controller_data(&priv->chip, priv);
priv->dev = &pdev->dev;
dev_set_drvdata(priv->dev, priv);
@@ -945,11 +963,21 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
if (ret)
goto err;
+ name = mtd->name;
+ ret = nand_set_release(&priv->chip, fsl_elbc_chip_release);
+ if (ret)
+ goto cleanup_nand;
+ kfree(name);
+
/* First look for RedBoot table or partitions on the command
* line, these take precedence over device tree information */
ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
- if (ret)
- goto cleanup_nand;
+ if (ret) {
+ nand_disconnect(&priv->chip);
+ fsl_elbc_chip_remove(priv);
+ mtd_put_master(mtd);
+ return ret;
+ }
pr_info("eLBC NAND device at 0x%llx, bank %d\n",
(unsigned long long)res.start, priv->bank);
@@ -959,32 +987,26 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
cleanup_nand:
nand_cleanup(&priv->chip);
err:
+ kfree(nand_to_mtd(&priv->chip)->name);
fsl_elbc_chip_remove(priv);
+ fsl_elbc_chip_release(&priv->chip);
return ret;
}
static void fsl_elbc_nand_remove(struct platform_device *pdev)
{
- struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev);
struct nand_chip *chip = &priv->chip;
int ret;
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
fsl_elbc_chip_remove(priv);
- mutex_lock(&fsl_elbc_nand_mutex);
- elbc_fcm_ctrl->counter--;
- if (!elbc_fcm_ctrl->counter) {
- fsl_lbc_ctrl_dev->nand = NULL;
- kfree(elbc_fcm_ctrl);
- }
- mutex_unlock(&fsl_elbc_nand_mutex);
-
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id fsl_elbc_nand_match[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 54/58] mtd: rawnand: fsl_ifc: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (52 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 53/58] mtd: rawnand: fsl_elbc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 55/58] mtd: rawnand: r852: " James Hilliard
` (3 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Separate active bank membership from ownership of the shared IFC NAND
controller. Retained chip references keep the controller lock and chip
metadata alive after removal, without retaining hardware mappings.
Disconnect before device-managed resources disappear and drop the shared
controller reference from each chip's final release callback.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_ifc_nand.c | 58 ++++++++++++++++++++++++++++++-------
1 file changed, 47 insertions(+), 11 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c
index cc75b6a67e07..18b747e73c3b 100644
--- a/drivers/mtd/nand/raw/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c
@@ -40,6 +40,8 @@ struct fsl_ifc_mtd {
/* overview of the fsl ifc controller */
struct fsl_ifc_nand_ctrl {
struct nand_controller controller;
+ struct kref refcnt;
+ unsigned int users;
struct fsl_ifc_mtd *chips[FSL_IFC_BANK_COUNT];
void __iomem *addr; /* Address of assigned IFC buffer */
@@ -961,11 +963,33 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
return 0;
}
-static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv)
+static void fsl_ifc_controller_release(struct kref *ref)
{
- ifc_nand_ctrl->chips[priv->bank] = NULL;
+ kfree(container_of(ref, struct fsl_ifc_nand_ctrl, refcnt));
+}
- return 0;
+static void fsl_ifc_chip_release(struct nand_chip *chip)
+{
+ struct fsl_ifc_nand_ctrl *ctrl;
+
+ ctrl = container_of(chip->controller, struct fsl_ifc_nand_ctrl, controller);
+ kfree(nand_get_controller_data(chip));
+ kref_put(&ctrl->refcnt, fsl_ifc_controller_release);
+}
+
+static DEFINE_MUTEX(fsl_ifc_nand_mutex);
+
+static void fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv)
+{
+ struct fsl_ifc_nand_ctrl *ctrl;
+
+ ctrl = container_of(priv->chip.controller, struct fsl_ifc_nand_ctrl, controller);
+ guard(mutex)(&fsl_ifc_nand_mutex);
+ ctrl->chips[priv->bank] = NULL;
+ if (!--ctrl->users) {
+ priv->ctrl->nand = NULL;
+ ifc_nand_ctrl = NULL;
+ }
}
static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank,
@@ -981,8 +1005,6 @@ static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank,
return (cspr & CSPR_BA) == convert_ifc_address(addr);
}
-static DEFINE_MUTEX(fsl_ifc_nand_mutex);
-
static int fsl_ifc_nand_probe(struct platform_device *dev)
{
struct fsl_ifc_runtime __iomem *ifc;
@@ -1016,30 +1038,35 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
return -ENODEV;
}
- priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
+ priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
mutex_lock(&fsl_ifc_nand_mutex);
if (!fsl_ifc_ctrl_dev->nand) {
- ifc_nand_ctrl = devm_kzalloc(fsl_ifc_ctrl_dev->dev, sizeof(*ifc_nand_ctrl),
- GFP_KERNEL);
+ ifc_nand_ctrl = kzalloc_obj(*ifc_nand_ctrl);
if (!ifc_nand_ctrl) {
mutex_unlock(&fsl_ifc_nand_mutex);
+ kfree(priv);
return -ENOMEM;
}
fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl;
nand_controller_init(&ifc_nand_ctrl->controller);
+ kref_init(&ifc_nand_ctrl->refcnt);
} else {
ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand;
+ kref_get(&ifc_nand_ctrl->refcnt);
}
+ ifc_nand_ctrl->users++;
mutex_unlock(&fsl_ifc_nand_mutex);
ifc_nand_ctrl->chips[bank] = priv;
priv->bank = bank;
priv->ctrl = fsl_ifc_ctrl_dev;
+ priv->chip.controller = &ifc_nand_ctrl->controller;
+ nand_set_controller_data(&priv->chip, priv);
priv->dev = &dev->dev;
priv->vbase = vbase;
@@ -1071,12 +1098,19 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
ret = nand_scan(&priv->chip, 1);
if (ret)
goto err;
+ ret = nand_set_release(&priv->chip, fsl_ifc_chip_release);
+ if (ret)
+ goto cleanup_nand;
/* First look for RedBoot table or partitions on the command
* line, these take precedence over device tree information */
ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0);
- if (ret)
- goto cleanup_nand;
+ if (ret) {
+ nand_disconnect(&priv->chip);
+ fsl_ifc_chip_remove(priv);
+ mtd_put_master(mtd);
+ return ret;
+ }
dev_info(priv->dev, "IFC NAND device at 0x%llx, bank %d\n",
(unsigned long long)res->start, priv->bank);
@@ -1087,6 +1121,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
nand_cleanup(&priv->chip);
err:
fsl_ifc_chip_remove(priv);
+ fsl_ifc_chip_release(&priv->chip);
return ret;
}
@@ -1099,9 +1134,10 @@ static void fsl_ifc_nand_remove(struct platform_device *dev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
fsl_ifc_chip_remove(priv);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id fsl_ifc_nand_match[] = {
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 55/58] mtd: rawnand: r852: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (53 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 54/58] mtd: rawnand: fsl_ifc: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:44 ` [PATCH v2 56/58] mtd: rawnand: nandsim: " James Hilliard
` (2 subsequent siblings)
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard, Maxim Levitsky
A removed card can still have open MTD handles. Reinitializing the same
nand_chip for a newly inserted card lets those handles access
replacement media, while freeing it during PCI removal leaves dangling
references.
Allocate a fresh chip for every insertion and separate SmartMedia
scanning from publication so lifetime ownership is installed first.
Disconnect the old chip before disabling its hardware, and retain it and
the controller until their final MTD reference is released. Stop
IRQ-driven card-work scheduling before destroying the workqueue during
PCI removal.
Cc: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/r852.c | 114 ++++++++++++++++++++++++---------------
drivers/mtd/nand/raw/r852.h | 1 +
drivers/mtd/nand/raw/sm_common.c | 15 ++----
drivers/mtd/nand/raw/sm_common.h | 2 +-
4 files changed, 76 insertions(+), 56 deletions(-)
diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c
index bf1c1be7f2aa..63f686d3a660 100644
--- a/drivers/mtd/nand/raw/r852.c
+++ b/drivers/mtd/nand/raw/r852.c
@@ -625,16 +625,34 @@ static void r852_update_media_status(struct r852_device *dev)
spin_unlock_irqrestore(&dev->irqlock, flags);
}
-/*
- * Register the nand device
- * Called when the card is detected
- */
+static void r852_chip_release(struct nand_chip *chip)
+{
+ kfree(chip);
+}
+
+/* Register a new chip instance for each inserted card. */
static int r852_register_nand_device(struct r852_device *dev)
{
- struct mtd_info *mtd = nand_to_mtd(dev->chip);
+ struct nand_chip *chip;
+ struct mtd_info *mtd;
+ int ret;
WARN_ON(dev->card_registered);
+ chip = kzalloc_obj(*chip);
+ if (!chip)
+ goto error1;
+
+ chip->legacy.cmd_ctrl = r852_cmdctl;
+ chip->legacy.waitfunc = r852_wait;
+ chip->legacy.dev_ready = r852_ready;
+ chip->legacy.read_byte = r852_read_byte;
+ chip->legacy.read_buf = r852_read_buf;
+ chip->legacy.write_buf = r852_write_buf;
+ chip->controller = &dev->controller;
+ nand_set_controller_data(chip, dev);
+ dev->chip = chip;
+ mtd = nand_to_mtd(chip);
mtd->dev.parent = &dev->pci_dev->dev;
if (dev->readonly)
@@ -642,8 +660,17 @@ static int r852_register_nand_device(struct r852_device *dev)
r852_engine_enable(dev);
- if (sm_register_device(mtd, dev->sm))
- goto error1;
+ ret = sm_scan_device(mtd, dev->sm);
+ if (ret)
+ goto free_chip;
+ ret = nand_set_release(chip, r852_chip_release);
+ if (ret) {
+ nand_cleanup(chip);
+ goto free_chip;
+ }
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret)
+ goto put_chip;
if (device_create_file(&mtd->dev, &dev_attr_media_type)) {
message("can't create media type sysfs attribute");
@@ -653,8 +680,15 @@ static int r852_register_nand_device(struct r852_device *dev)
dev->card_registered = 1;
return 0;
error3:
- WARN_ON(mtd_device_unregister(nand_to_mtd(dev->chip)));
- nand_cleanup(dev->chip);
+ WARN_ON(mtd_device_unregister(mtd));
+put_chip:
+ nand_disconnect(chip);
+ dev->chip = NULL;
+ mtd_put_master(mtd);
+ goto error1;
+free_chip:
+ dev->chip = NULL;
+ kfree(chip);
error1:
/* Force card redetect */
dev->card_detected = 0;
@@ -667,16 +701,19 @@ static int r852_register_nand_device(struct r852_device *dev)
static void r852_unregister_nand_device(struct r852_device *dev)
{
- struct mtd_info *mtd = nand_to_mtd(dev->chip);
+ struct mtd_info *mtd;
if (!dev->card_registered)
return;
+ mtd = nand_to_mtd(dev->chip);
device_remove_file(&mtd->dev, &dev_attr_media_type);
WARN_ON(mtd_device_unregister(mtd));
- nand_cleanup(dev->chip);
+ nand_disconnect(dev->chip);
r852_engine_disable(dev);
+ dev->chip = NULL;
dev->card_registered = 0;
+ mtd_put_master(mtd);
}
/* Card state updater */
@@ -734,7 +771,7 @@ static irqreturn_t r852_irq(int irq, void *data)
card_status = r852_read_reg(dev, R852_CARD_IRQ_STA);
r852_write_reg(dev, R852_CARD_IRQ_STA, card_status);
- if (card_status & (R852_CARD_IRQ_INSERT|R852_CARD_IRQ_REMOVE)) {
+ if (card_status & (R852_CARD_IRQ_INSERT | R852_CARD_IRQ_REMOVE)) {
ret = IRQ_HANDLED;
dev->card_detected = !!(card_status & R852_CARD_IRQ_INSERT);
@@ -752,8 +789,10 @@ static irqreturn_t r852_irq(int irq, void *data)
/* let, card state to settle a bit, and then do the work */
dev->card_unstable = 1;
- queue_delayed_work(dev->card_workqueue,
- &dev->card_detect_work, msecs_to_jiffies(100));
+ if (!dev->removing)
+ queue_delayed_work(dev->card_workqueue,
+ &dev->card_detect_work,
+ msecs_to_jiffies(100));
goto out;
}
@@ -841,10 +880,14 @@ static const struct nand_controller_ops r852_ops = {
.attach_chip = r852_attach_chip,
};
+static void r852_controller_release(struct nand_controller *controller)
+{
+ kfree(container_of(controller, struct r852_device, controller));
+}
+
static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
{
int error;
- struct nand_chip *chip;
struct r852_device *dev;
/* pci initialization */
@@ -866,42 +909,28 @@ static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
error = -ENOMEM;
- /* init nand chip, but register it only on card insert */
- chip = kzalloc_obj(struct nand_chip);
-
- if (!chip)
- goto error4;
-
- /* commands */
- chip->legacy.cmd_ctrl = r852_cmdctl;
- chip->legacy.waitfunc = r852_wait;
- chip->legacy.dev_ready = r852_ready;
-
- /* I/O */
- chip->legacy.read_byte = r852_read_byte;
- chip->legacy.read_buf = r852_read_buf;
- chip->legacy.write_buf = r852_write_buf;
-
/* init our device structure */
dev = kzalloc_obj(struct r852_device);
if (!dev)
- goto error5;
+ goto error4;
+ error = devm_nand_controller_init(&pci_dev->dev, &dev->controller,
+ r852_controller_release);
+ if (error)
+ goto error4;
- nand_set_controller_data(chip, dev);
- dev->chip = chip;
dev->pci_dev = pci_dev;
pci_set_drvdata(pci_dev, dev);
- nand_controller_init(&dev->controller);
dev->controller.ops = &r852_ops;
- chip->controller = &dev->controller;
dev->bounce_buffer = dma_alloc_coherent(&pci_dev->dev, R852_DMA_LEN,
&dev->phys_bounce_buffer, GFP_KERNEL);
- if (!dev->bounce_buffer)
+ if (!dev->bounce_buffer) {
+ error = -ENOMEM;
goto error6;
+ }
error = -ENODEV;
@@ -961,9 +990,6 @@ static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
dma_free_coherent(&pci_dev->dev, R852_DMA_LEN, dev->bounce_buffer,
dev->phys_bounce_buffer);
error6:
- kfree(dev);
-error5:
- kfree(chip);
error4:
pci_release_regions(pci_dev);
error3:
@@ -976,6 +1002,11 @@ static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
static void r852_remove(struct pci_dev *pci_dev)
{
struct r852_device *dev = pci_get_drvdata(pci_dev);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->irqlock, flags);
+ dev->removing = true;
+ spin_unlock_irqrestore(&dev->irqlock, flags);
/* Stop detect workqueue -
we are going to unregister the device anyway*/
@@ -995,9 +1026,6 @@ static void r852_remove(struct pci_dev *pci_dev)
dma_free_coherent(&pci_dev->dev, R852_DMA_LEN, dev->bounce_buffer,
dev->phys_bounce_buffer);
- kfree(dev->chip);
- kfree(dev);
-
/* Shutdown the PCI device */
pci_release_regions(pci_dev);
pci_disable_device(pci_dev);
diff --git a/drivers/mtd/nand/raw/r852.h b/drivers/mtd/nand/raw/r852.h
index 96fe301d15da..a89ab61ea51e 100644
--- a/drivers/mtd/nand/raw/r852.h
+++ b/drivers/mtd/nand/raw/r852.h
@@ -128,6 +128,7 @@ struct r852_device {
struct delayed_work card_detect_work;
struct workqueue_struct *card_workqueue;
int card_registered; /* card registered with mtd */
+ bool removing; /* stop card work during PCI removal */
int card_detected; /* card detected in slot */
int card_unstable; /* whenever the card is inserted,
is not known yet */
diff --git a/drivers/mtd/nand/raw/sm_common.c b/drivers/mtd/nand/raw/sm_common.c
index e238784c8c3e..a79e3c3c77d1 100644
--- a/drivers/mtd/nand/raw/sm_common.c
+++ b/drivers/mtd/nand/raw/sm_common.c
@@ -182,28 +182,19 @@ static const struct nand_controller_ops sm_controller_ops = {
.attach_chip = sm_attach_chip,
};
-int sm_register_device(struct mtd_info *mtd, int smartmedia)
+int sm_scan_device(struct mtd_info *mtd, int smartmedia)
{
struct nand_chip *chip = mtd_to_nand(mtd);
struct nand_flash_dev *flash_ids;
- int ret;
chip->options |= NAND_SKIP_BBTSCAN;
/* Scan for card properties */
chip->legacy.dummy_controller.ops = &sm_controller_ops;
flash_ids = smartmedia ? nand_smartmedia_flash_ids : nand_xd_flash_ids;
- ret = nand_scan_with_ids(chip, 1, flash_ids);
- if (ret)
- return ret;
-
- ret = mtd_device_register(mtd, NULL, 0);
- if (ret)
- nand_cleanup(chip);
-
- return ret;
+ return nand_scan_with_ids(chip, 1, flash_ids);
}
-EXPORT_SYMBOL_GPL(sm_register_device);
+EXPORT_SYMBOL_GPL(sm_scan_device);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Maxim Levitsky <maximlevitsky@gmail.com>");
diff --git a/drivers/mtd/nand/raw/sm_common.h b/drivers/mtd/nand/raw/sm_common.h
index 57fc9f86f9ee..d8a330d6b730 100644
--- a/drivers/mtd/nand/raw/sm_common.h
+++ b/drivers/mtd/nand/raw/sm_common.h
@@ -33,7 +33,7 @@ struct sm_oob {
#define SM_SMALL_OOB_SIZE 8
-int sm_register_device(struct mtd_info *mtd, int smartmedia);
+int sm_scan_device(struct mtd_info *mtd, int smartmedia);
static inline int sm_sector_valid(struct sm_oob *oob)
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 56/58] mtd: rawnand: nandsim: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (54 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 55/58] mtd: rawnand: r852: " James Hilliard
@ 2026-11-10 7:44 ` James Hilliard
2026-11-10 7:45 ` [PATCH v2 57/58] mtd: rawnand: cs553x: " James Hilliard
2026-11-10 7:45 ` [PATCH v2 58/58] mtd: rawnand: diskonchip: " James Hilliard
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:44 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
MTD device references can outlive device unregistration even when no
open file pins the simulator module. Disconnect before freeing the
simulated media, and retain the chip metadata until the final MTD put.
The lifetime core waits for remaining device references during module
unload.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/nandsim.c | 24 ++++++++++++++++++++----
1 file changed, 20 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c
index 6716eafb4b00..0c8c741b2b85 100644
--- a/drivers/mtd/nand/raw/nandsim.c
+++ b/drivers/mtd/nand/raw/nandsim.c
@@ -2254,6 +2254,11 @@ static const struct nand_controller_ops ns_controller_ops = {
.exec_op = ns_exec_op,
};
+static void ns_chip_release(struct nand_chip *chip)
+{
+ kfree(nand_get_controller_data(chip));
+}
+
/*
* Module initialization function
*/
@@ -2263,6 +2268,7 @@ static int __init ns_init_module(void)
struct nand_chip *chip;
struct nandsim *ns;
int ret;
+ bool retained = false;
if (bus_width != 8 && bus_width != 16) {
NS_ERR("wrong bus width (%d), use only 8 or 16\n", bus_width);
@@ -2377,6 +2383,10 @@ static int __init ns_init_module(void)
ret = ns_parse_badblocks(ns, nsmtd);
if (ret)
goto free_ns_object;
+ ret = nand_set_release(chip, ns_chip_release);
+ if (ret)
+ goto free_ns_object;
+ retained = true;
/* Register NAND partitions */
ret = mtd_device_register(nsmtd, &ns->partitions[0], ns->nbparts);
@@ -2392,11 +2402,16 @@ static int __init ns_init_module(void)
unregister_mtd:
WARN_ON(mtd_device_unregister(nsmtd));
free_ns_object:
+ if (retained)
+ nand_disconnect(chip);
ns_free(ns);
free_ebw:
kfree(erase_block_wear);
cleanup_nand:
- nand_cleanup(chip);
+ if (retained)
+ mtd_put_master(nsmtd);
+ else
+ nand_cleanup(chip);
free_gp_list:
list_for_each_safe(pos, n, &grave_pages) {
list_del(pos);
@@ -2413,7 +2428,8 @@ static int __init ns_init_module(void)
kfree(list_entry(pos, struct weak_block, list));
}
free_ns_struct:
- kfree(ns);
+ if (!retained)
+ kfree(ns);
return ret;
}
@@ -2431,9 +2447,9 @@ static void __exit ns_cleanup_module(void)
ns_debugfs_remove(ns);
WARN_ON(mtd_device_unregister(nsmtd));
+ nand_disconnect(chip);
ns_free(ns);
kfree(erase_block_wear);
- nand_cleanup(chip);
list_for_each_safe(pos, n, &grave_pages) {
list_del(pos);
@@ -2450,7 +2466,7 @@ static void __exit ns_cleanup_module(void)
kfree(list_entry(pos, struct weak_block, list));
}
- kfree(ns);
+ mtd_put_master(nsmtd);
}
module_exit(ns_cleanup_module);
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 57/58] mtd: rawnand: cs553x: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (55 preceding siblings ...)
2026-11-10 7:44 ` [PATCH v2 56/58] mtd: rawnand: nandsim: " James Hilliard
@ 2026-11-10 7:45 ` James Hilliard
2026-11-10 7:45 ` [PATCH v2 58/58] mtd: rawnand: diskonchip: " James Hilliard
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:45 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
Keep chip metadata alive while MTD device references remain after module
removal unregisters the device. Disconnect before unmapping the
controller, and release the backing allocation at the final MTD put.
Check registration failures and release the corresponding controller
without leaving it in the registered-device array.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cs553x_nand.c | 46 ++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c
index 0b872b1e3b04..7419308a740d 100644
--- a/drivers/mtd/nand/raw/cs553x_nand.c
+++ b/drivers/mtd/nand/raw/cs553x_nand.c
@@ -257,12 +257,18 @@ static const struct nand_controller_ops cs553x_nand_controller_ops = {
.attach_chip = cs553x_attach_chip,
};
+static void cs553x_chip_release(struct nand_chip *chip)
+{
+ kfree(container_of(chip, struct cs553x_nand_controller, chip));
+}
+
static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
{
struct cs553x_nand_controller *controller;
int err = 0;
struct nand_chip *this;
struct mtd_info *new_mtd;
+ const char *name;
pr_notice("Probing CS553x NAND controller CS#%d at %sIO 0x%08lx\n",
cs, mmio ? "MM" : "P", adr);
@@ -309,6 +315,13 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
err = nand_scan(this, 1);
if (err)
goto out_free;
+ name = new_mtd->name;
+ err = nand_set_release(this, cs553x_chip_release);
+ if (err) {
+ nand_cleanup(this);
+ goto out_free;
+ }
+ kfree(name);
controllers[cs] = controller;
goto out;
@@ -344,6 +357,7 @@ static int __init cs553x_init(void)
{
int err = -ENXIO;
int i;
+ int registered = 0;
uint64_t val;
/* If the CPU isn't a Geode GX or LX, abort */
@@ -374,14 +388,22 @@ static int __init cs553x_init(void)
do mtdconcat etc. if we want to. */
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
if (controllers[i]) {
- /* If any devices registered, return success. Else the last error. */
- mtd_device_register(nand_to_mtd(&controllers[i]->chip),
- NULL, 0);
- err = 0;
+ struct cs553x_nand_controller *controller = controllers[i];
+ struct mtd_info *mtd = nand_to_mtd(&controller->chip);
+
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err) {
+ nand_disconnect(&controller->chip);
+ iounmap(controller->mmio);
+ controllers[i] = NULL;
+ mtd_put_master(mtd);
+ } else {
+ registered++;
+ }
}
}
- return err;
+ return registered ? 0 : err;
}
module_init(cs553x_init);
@@ -392,25 +414,25 @@ static void __exit cs553x_cleanup(void)
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
struct cs553x_nand_controller *controller = controllers[i];
- struct nand_chip *this = &controller->chip;
- struct mtd_info *mtd = nand_to_mtd(this);
+ struct nand_chip *this;
+ struct mtd_info *mtd;
int ret;
- if (!mtd)
+ if (!controller)
continue;
+ this = &controller->chip;
+ mtd = nand_to_mtd(this);
/* Release resources, unregister device */
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(this);
- kfree(mtd->name);
+ nand_disconnect(this);
controllers[i] = NULL;
/* unmap physical address */
iounmap(controller->mmio);
- /* Free the MTD device structure */
- kfree(controller);
+ mtd_put_master(mtd);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread* [PATCH v2 58/58] mtd: rawnand: diskonchip: retain chip state across removal
2026-11-10 7:44 [PATCH v2 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (56 preceding siblings ...)
2026-11-10 7:45 ` [PATCH v2 57/58] mtd: rawnand: cs553x: " James Hilliard
@ 2026-11-10 7:45 ` James Hilliard
57 siblings, 0 replies; 60+ messages in thread
From: James Hilliard @ 2026-11-10 7:45 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, James Hilliard
MTD device references may outlive unregistration during module removal.
Disconnect before unmapping the device and freeing the hardware ECC
codec, while retaining chip metadata until the final MTD reference is
released. Install lifetime ownership before the late initializer
publishes partitions.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/diskonchip.c | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/diskonchip.c b/drivers/mtd/nand/raw/diskonchip.c
index 540b6baf8bb1..a4338b51b49d 100644
--- a/drivers/mtd/nand/raw/diskonchip.c
+++ b/drivers/mtd/nand/raw/diskonchip.c
@@ -1296,6 +1296,11 @@ static const struct nand_controller_ops doc2001plus_ops = {
.attach_chip = doc200x_attach_chip,
};
+static void doc_chip_release(struct nand_chip *chip)
+{
+ kfree(chip);
+}
+
static int __init doc_probe(unsigned long physadr)
{
struct nand_chip *nand = NULL;
@@ -1494,13 +1499,21 @@ static int __init doc_probe(unsigned long physadr)
ret = nand_scan(nand, numchips);
if (ret)
goto fail;
-
- ret = doc->late_init(mtd);
+ ret = nand_set_release(nand, doc_chip_release);
if (ret) {
nand_cleanup(nand);
goto fail;
}
+ ret = doc->late_init(mtd);
+ if (ret) {
+ nand_disconnect(nand);
+ free_rs(doc->rs_decoder);
+ iounmap(virtadr);
+ mtd_put_master(mtd);
+ goto error_ioremap;
+ }
+
/* Success! */
doclist = mtd;
return 0;
@@ -1535,11 +1548,11 @@ static void release_nanddoc(void)
nextmtd = doc->nextdoc;
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(nand);
+ nand_disconnect(nand);
iounmap(doc->virtadr);
release_mem_region(doc->physadr, DOC_IOREMAP_LEN);
free_rs(doc->rs_decoder);
- kfree(nand);
+ mtd_put_master(mtd);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 60+ messages in thread