* [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal
@ 2026-11-12 1:27 James Hilliard
2026-11-12 1:27 ` [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns James Hilliard
` (57 more replies)
0 siblings, 58 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Srinivas Kandagatla, Tze Yee Ng, Richard Cochran, Jonathan Lemon,
Vadim Fedorenko, Yuqiang Sun, Amit Kumar Mahapatra, Luca Ceresoli,
Bernhard Frauendienst, Gustavo A. R. Silva, Kees Cook,
linux-hardening, Arseniy Krasnov, Boris Brezillon, Brian Norris,
Jernej Skrabec, linux-arm-kernel, linux-sunxi, Samuel Holland,
Chen-Yu Tsai, David Woodhouse, Thomas Gleixner, Vitaly Wool,
David Woodhouse, Russell King, Mike Rapoport, Alexandre Torgue,
Christophe Kerello, Maxime Coquelin, Jason Roberts,
Boris Brezillon, Michal Simek, Mason Yang,
Ramuthevar Vadivel Murugan, Lucas Stach, Dmitry Osipenko,
Jonathan Hunter, linux-tegra, Stefan Agner, Thierry Reding,
AngeloGioacchino Del Regno, Jorge Ramirez-Ortiz, linux-mediatek,
Matthias Brugger, Heiko Stuebner, linux-rockchip, Yifeng Zhao,
Alexandre Belloni, Claudiu Beznea, Nicolas Ferre, Keguang Zhang,
linux-mips, Shan-Chun Hung, Jacky Huang, Fabio Estevam, Frank Li,
imx, Pengutronix Kernel Team, Sascha Hauer, Han Xu,
linux-renesas-soc, Jerome Brunet, Kevin Hilman, Liang Yang,
linux-amlogic, Martin Blumenstingl, Neil Armstrong, linux-arm-msm,
Manivannan Sadhasivam, Broadcom internal kernel review list,
Kamal Dasu, Piotr Wojtaszczyk, Vladimir Zapolskiy, Harvey Hunt,
Paul Cercueil, Maxim Levitsky
An MTD handle can outlive controller unbind. Its metadata and NAND locks
must remain valid, including when a writable file closes and calls sync,
while further hardware access must be rejected after disconnection.
This builds on earlier MTD removal-lifetime work. Commit 19bfa9ebebb5
("mtd: use refcount to prevent corruption") added MTD reference counting
[0], but retaining an MTD reference does not by itself retain the
driver-owned allocation containing the master. The read/unbind discussion
also covered raw NAND [1] and draining active operations [2]. Tze Yee Ng's
SPI-NOR series addresses the corresponding SPI-NOR paths [3].
Following Greg's review [7], separate class-device data from the MTD
backing allocation. Delete the device's interfaces and drain NVMEM and
class PM callbacks before releasing backing data. Bare device references
retain an independently allocated device, whose type, PM dispatch and
final release are built in even when MTD is modular. NVMEM similarly
disconnects and drains provider and layout callbacks before unregister
returns; retained consumers keep core-owned metadata and receive -ENODEV
on subsequent access.
Introduce opt-in backing-object ownership for MTD masters. Registered
nodes and acquired MTD handles retain the chip, while nested partitions
also retain their parent metadata. Unregister first so removal notifiers
can finish I/O, then disconnect raw NAND and drain admitted operations.
Hardware resources can be released without waiting for open files to close.
Virtual concatenations serialize publication and removal across physical
controllers. Disconnect and drain a published concatenation before any
component can disappear, then release its component references. An old
handle keeps the disconnected virtual device, independently of a later
replacement.
Run NAND and controller-specific cleanup during disconnection, while
the controller driver is still bound. Snapshot the OOB layout in core-owned
data before publication so metadata queries remain valid without driver
callbacks. The NAND core frees retained chip and controller allocations;
the final MTD put never calls back into the controller module. Ordinary
MTD and NVMEM handles retain the NAND core that implements their
operations, rather than the hardware driver.
Share allocation ownership across the raw-NAND drivers, including
partially published probes. Shared controllers remain allocated until
their last retained chip is released. R852 allocates a new chip for each
card, and NDFC allocates controller state per binding.
Based on NAND next at 30daaf3e7897, with Tze Yee Ng's module-reference
ordering fix [4], Karl Mehltretter's OTP cleanup fix [6], and the separately
posted registration, operation-locking and controller probe/teardown fixes
identified by the prerequisite metadata. TLA+ models and their assumptions
and coverage limits are available in [5].
[0] https://lore.kernel.org/r/20230620131905.648089-2-alexander.usyskin@intel.com
[1] https://lore.kernel.org/r/CAND4H7dz94Gsi_tXSQmLNme9uROnouOUwuCX9BW_+RCc2ZFDqA@mail.gmail.com
[2] https://lore.kernel.org/r/mafs0bjs73tbq.fsf@kernel.org
[3] https://lore.kernel.org/r/20261022035352.52129-1-tze.yee.ng@altera.com
[4] https://lore.kernel.org/r/20261022035352.52129-2-tze.yee.ng@altera.com
[5] https://github.com/jameshilliard/linux-h616-nand-tla
[6] https://lore.kernel.org/r/20260816163027.10044-1-kmehltretter@gmail.com
[7] https://lore.kernel.org/r/2026111037-stir-hermit-cb21@gregkh
Assisted-by: Codex:gpt-6-astra
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
Changes in v3:
- Finish NAND and driver cleanup during disconnection, leaving final
allocation release in the NAND core, following Greg's networking example.
- Snapshot OOB layouts before publication and drain complete BBM/BBT
updates before releasing their working state.
- Separate MTD class-device allocation and final release from backing-object
ownership, following Greg's review.
- Drain class PM callbacks under the device lock before detaching backing
data; keep device-type and PM dispatch storage with the built-in device.
- Drain NVMEM provider and layout callbacks during unregister, retaining
core-owned metadata for old consumers and retiring cells across rebind.
- Keep NVMEM consumer-owner references, with converted raw NAND
providers naming the NAND core instead of the controller module.
Release write-protect GPIOs once provider callbacks are drained.
- Acquire an MTD handle for PTP OCP firmware updates through a class device.
- Drain concatenation operations before releasing components, including
concurrent removal of masters without opt-in lifetime ownership.
- Use scoped cleanup consistently in Tegra probe and add the introducing
commits for Socrates and FSL UPM, following Sashiko's review.
- Link to v2: https://patch.msgid.link/20261110-submit-rawnand-retained-handle-lifetime-v1-v2-0-9137d08f05df@gmail.com
Changes in v2:
- Keep the MTD device callback owner pinned through callback return,
with the final module put performed by the device core.
- Retain virtual-concat components across removal and rebind, serialize
staging/publication and unwind incomplete partition registrations.
- Balance Tegra's probe-time child-node reference separately from the
reference retained for the chip lifetime.
- Refresh the prerequisite stack to the separately posted fixes, using
the latest QCOM child-probe revision.
- Keep the lifetime changes on top of those fixes: preserve QCOM scan
before publication, R852 per-card initialization and Intel probe
hardware shutdown.
- Fold Macronix disconnection handling into the NAND disconnect patch;
its operation locking is supplied by the posted prerequisite.
- Use Karl Mehltretter's posted OTP cleanup fix as a prerequisite in
place of the duplicate preparatory patch.
- Keep the NVMEM context callback module pinned until its final callback
returns, independently of the provider's consumer owner.
- Use the posted fixes for dynamic partition publication and partition-node
ownership. Keep master get/put accounting independent of
later master registration in the MTD ownership patch.
- Propagate disconnection through BBT error recovery without marking
the eraseblock bad.
- Make suspend of a disconnected, retained NAND device a no-op.
- Handle R852 card events during removal while preventing new work from
being queued.
- Initialize Denali and Atmel cleanup variables at allocation and add
the introducing-commit trailers for Orion and MPC5121.
- Link to v1: https://patch.msgid.link/20261107-submit-rawnand-retained-handle-lifetime-v1-v1-0-0181ef0cc4c8@gmail.com
To: Miquel Raynal <miquel.raynal@bootlin.com>
To: Richard Weinberger <richard@nod.at>
To: Vignesh Raghavendra <vigneshr@ti.com>
Cc: linux-kernel@vger.kernel.org
Cc: linux-mtd@lists.infradead.org
---
James Hilliard (58):
nvmem: disconnect provider callbacks before unregister returns
mtd: acquire an MTD handle from a referenced class device
mtd: separate class devices from retained backing objects
mtd: retain virtual concatenations across component removal
mtd: rawnand: separate hardware disconnection from cleanup
mtd: rawnand: share retained chip and controller ownership
mtd: rawnand: sunxi: retain chip state until the final MTD put
mtd: rawnand: plat_nand: retain open-handle state on removal
mtd: rawnand: gpio: retain open-handle state on removal
mtd: rawnand: stm32_fmc2: retain open-handle state on removal
mtd: rawnand: denali: retain chips across controller removal
mtd: rawnand: arasan: retain chips across controller removal
mtd: rawnand: mxic: retain open-handle state on removal
mtd: rawnand: intel: retain open-handle state on removal
mtd: rawnand: tegra: retain open-handle state on removal
mtd: rawnand: mtk: retain chips across controller removal
mtd: rawnand: rockchip: retain chips across controller removal
mtd: rawnand: atmel: retain chips across controller removal
mtd: rawnand: orion: retain open-handle state on removal
mtd: rawnand: socrates: retain open-handle state on removal
mtd: rawnand: fsl_upm: retain open-handle state on removal
mtd: rawnand: pasemi: retain open-handle state on removal
mtd: rawnand: ams-delta: retain open-handle state on removal
mtd: rawnand: xway: retain open-handle state on removal
mtd: rawnand: hisi504: retain open-handle state on removal
mtd: rawnand: vf610: retain open-handle state on removal
mtd: rawnand: technologic: retain open-handle state on removal
mtd: rawnand: loongson: retain open-handle state on removal
mtd: rawnand: ma35d1: retain open-handle state on removal
mtd: rawnand: pl35x: retain open-handle state on removal
mtd: rawnand: mxc: retain open-handle state on removal
mtd: rawnand: fsmc: retain open-handle state on removal
mtd: rawnand: gpmi: retain open-handle state on removal
mtd: rawnand: omap2: retain open-handle state on removal
mtd: rawnand: marvell: retain open-handle state on removal
mtd: rawnand: renesas: retain open-handle state on removal
mtd: rawnand: meson: retain open-handle state on removal
mtd: rawnand: cadence: retain open-handle state on removal
mtd: rawnand: qcom: retain open-handle state on removal
mtd: rawnand: cafe: retain open-handle state on removal
mtd: rawnand: brcmnand: retain open-handle state on removal
mtd: rawnand: au1550nd: retain chip state across removal
mtd: rawnand: sharpsl: retain chip state across removal
mtd: rawnand: davinci: retain chip state across removal
mtd: rawnand: sh_flctl: retain chip state across removal
mtd: rawnand: lpc32xx_slc: retain chip state across removal
mtd: rawnand: lpc32xx_mlc: retain chip state across removal
mtd: rawnand: ndfc: retain chip state across removal
mtd: rawnand: txx9ndfmc: retain chip state across removal
mtd: rawnand: ingenic: retain chip state across removal
mtd: rawnand: mpc5121: retain chip state across removal
mtd: rawnand: bcm47xxnflash: retain chip state across removal
mtd: rawnand: fsl_elbc: retain chip state across removal
mtd: rawnand: fsl_ifc: retain chip state across removal
mtd: rawnand: r852: retain chip state across removal
mtd: rawnand: nandsim: retain chip state across removal
mtd: rawnand: cs553x: retain chip state across removal
mtd: rawnand: diskonchip: retain chip state across removal
drivers/Makefile | 2 +-
drivers/media/pci/cobalt/cobalt-flash.c | 2 +-
drivers/mtd/Kconfig | 5 +
drivers/mtd/Makefile | 1 +
drivers/mtd/devices/bcm47xxsflash.c | 2 +-
drivers/mtd/devices/docg3.c | 4 +-
drivers/mtd/devices/mchp23k256.c | 2 +-
drivers/mtd/devices/mchp48l640.c | 2 +-
drivers/mtd/devices/mtd_dataflash.c | 2 +-
drivers/mtd/devices/mtd_intel_dg.c | 30 +-
drivers/mtd/devices/powernv_flash.c | 4 +-
drivers/mtd/devices/spear_smi.c | 2 +-
drivers/mtd/devices/sst25l.c | 2 +-
drivers/mtd/devices/st_spi_fsm.c | 8 +-
drivers/mtd/hyperbus/hyperbus-core.c | 2 +-
drivers/mtd/lpddr/lpddr2_nvm.c | 2 +-
drivers/mtd/maps/lantiq-flash.c | 2 +-
drivers/mtd/maps/physmap-core.c | 2 +-
drivers/mtd/maps/plat-ram.c | 2 +-
drivers/mtd/maps/pxa2xx-flash.c | 2 +-
drivers/mtd/maps/sa1100-flash.c | 2 +-
drivers/mtd/maps/vmu-flash.c | 2 +-
drivers/mtd/mtd_blkdevs.c | 2 +-
drivers/mtd/mtd_virt_concat.c | 526 +++++++++++----------
drivers/mtd/mtdconcat.c | 103 +++-
drivers/mtd/mtdcore.c | 304 +++++++-----
drivers/mtd/mtdcore.h | 5 +
drivers/mtd/mtddevice.c | 59 +++
drivers/mtd/mtddevice.h | 12 +
drivers/mtd/mtdpart.c | 29 +-
drivers/mtd/mtdpstore.c | 50 +-
drivers/mtd/nand/ecc.c | 3 +-
drivers/mtd/nand/onenand/generic.c | 2 +-
drivers/mtd/nand/onenand/onenand_omap2.c | 2 +-
drivers/mtd/nand/onenand/onenand_samsung.c | 2 +-
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/ams-delta.c | 28 +-
drivers/mtd/nand/raw/arasan-nand-controller.c | 49 +-
drivers/mtd/nand/raw/atmel/nand-controller.c | 44 +-
drivers/mtd/nand/raw/au1550nd.c | 23 +-
drivers/mtd/nand/raw/bcm47xxnflash/main.c | 18 +-
drivers/mtd/nand/raw/brcmnand/brcmnand.c | 65 ++-
drivers/mtd/nand/raw/cadence-nand-controller.c | 51 +-
drivers/mtd/nand/raw/cafe_nand.c | 27 +-
drivers/mtd/nand/raw/cs553x_nand.c | 44 +-
drivers/mtd/nand/raw/davinci_nand.c | 26 +-
drivers/mtd/nand/raw/denali.c | 28 +-
drivers/mtd/nand/raw/denali_dt.c | 13 +-
drivers/mtd/nand/raw/denali_pci.c | 9 +-
drivers/mtd/nand/raw/diskonchip.c | 17 +-
drivers/mtd/nand/raw/fsl_elbc_nand.c | 56 ++-
drivers/mtd/nand/raw/fsl_ifc_nand.c | 48 +-
drivers/mtd/nand/raw/fsl_upm.c | 20 +-
drivers/mtd/nand/raw/fsmc_nand.c | 19 +-
drivers/mtd/nand/raw/gpio.c | 18 +-
drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 19 +-
drivers/mtd/nand/raw/hisi504_nand.c | 20 +-
drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c | 41 +-
drivers/mtd/nand/raw/intel-nand-controller.c | 20 +-
drivers/mtd/nand/raw/internals.h | 2 +-
drivers/mtd/nand/raw/loongson-nand-controller.c | 21 +-
drivers/mtd/nand/raw/lpc32xx_mlc.c | 38 +-
drivers/mtd/nand/raw/lpc32xx_slc.c | 40 +-
drivers/mtd/nand/raw/marvell_nand.c | 46 +-
drivers/mtd/nand/raw/meson_nand.c | 49 +-
drivers/mtd/nand/raw/mpc5121_nfc.c | 26 +-
drivers/mtd/nand/raw/mtk_nand.c | 40 +-
drivers/mtd/nand/raw/mxc_nand.c | 23 +-
drivers/mtd/nand/raw/mxic_nand.c | 23 +-
drivers/mtd/nand/raw/nand_base.c | 103 +++-
drivers/mtd/nand/raw/nand_bbt.c | 24 +-
drivers/mtd/nand/raw/nand_lifetime.c | 246 ++++++++++
drivers/mtd/nand/raw/nand_macronix.c | 8 +-
drivers/mtd/nand/raw/nand_onfi.c | 4 +-
drivers/mtd/nand/raw/nandsim.c | 21 +-
drivers/mtd/nand/raw/ndfc.c | 34 +-
.../mtd/nand/raw/nuvoton-ma35d1-nand-controller.c | 42 +-
drivers/mtd/nand/raw/omap2.c | 27 +-
drivers/mtd/nand/raw/orion_nand.c | 24 +-
drivers/mtd/nand/raw/pasemi_nand.c | 22 +-
drivers/mtd/nand/raw/pl35x-nand-controller.c | 38 +-
drivers/mtd/nand/raw/plat_nand.c | 20 +-
drivers/mtd/nand/raw/qcom_nandc.c | 39 +-
drivers/mtd/nand/raw/r852.c | 111 +++--
drivers/mtd/nand/raw/r852.h | 1 +
drivers/mtd/nand/raw/renesas-nand-controller.c | 37 +-
drivers/mtd/nand/raw/rockchip-nand-controller.c | 45 +-
drivers/mtd/nand/raw/sh_flctl.c | 22 +-
drivers/mtd/nand/raw/sharpsl.c | 22 +-
drivers/mtd/nand/raw/sm_common.c | 15 +-
drivers/mtd/nand/raw/sm_common.h | 2 +-
drivers/mtd/nand/raw/socrates_nand.c | 19 +-
drivers/mtd/nand/raw/stm32_fmc2_nand.c | 19 +-
drivers/mtd/nand/raw/sunxi_nand.c | 46 +-
drivers/mtd/nand/raw/technologic-nand-controller.c | 22 +-
drivers/mtd/nand/raw/tegra_nand.c | 27 +-
drivers/mtd/nand/raw/txx9ndfmc.c | 41 +-
drivers/mtd/nand/raw/vf610_nfc.c | 20 +-
drivers/mtd/nand/raw/xway_nand.c | 24 +-
drivers/mtd/nand/spi/core.c | 2 +-
drivers/mtd/nand/spi/esmt.c | 2 +-
drivers/mtd/nand/spi/micron.c | 4 +-
drivers/mtd/nand/spi/otp.c | 2 +-
drivers/mtd/spi-nor/core.c | 2 +-
drivers/mtd/ubi/build.c | 2 +-
drivers/nvmem/Kconfig | 1 +
drivers/nvmem/core.c | 297 ++++++++----
drivers/nvmem/internals.h | 13 +-
drivers/nvmem/layouts.c | 2 +
drivers/ptp/ptp_ocp.c | 7 +-
include/linux/mtd/concat.h | 22 +-
include/linux/mtd/mtd.h | 21 +-
include/linux/mtd/rawnand.h | 37 +-
include/linux/nvmem-provider.h | 3 +
114 files changed, 2551 insertions(+), 1186 deletions(-)
---
base-commit: 30daaf3e7897b4a8d9b72e5b0c86dd6e2a4d7d85
change-id: 20261104-submit-rawnand-retained-handle-lifetime-v1-0b4225e5ce0b
prerequisite-patch-id: a51b9cb4077105499133c413d58c7ad21d973b38
prerequisite-patch-id: b3679cda4dcb45ce7712bffe2dd0576b254df44d
prerequisite-patch-id: 07b5c70e44ad951d20eea9478c98fda35ec765c0
prerequisite-patch-id: abea50275922fe359d72ca4de5cbc211b04bbd57
prerequisite-patch-id: 57776b214ffb0331319f024412f21d35e4ca9056
prerequisite-patch-id: 09be2baaffe1f053704263cc4b0d39f2faa4f49c
prerequisite-patch-id: c3ad85600dc897ac310be450d8daa2275fb91ee6
prerequisite-patch-id: 4a85600553ddcc7db5a9537efd905a71cd623f65
prerequisite-patch-id: 579990ee4a49297efc5a593b00efb9cd73fa15e8
prerequisite-patch-id: 2ee61a8a32fd147d52e0f9d03758634912d8bfef
prerequisite-patch-id: 1487df8ce8e6c48471fd77c7470ce9de582dfe3c
prerequisite-patch-id: 5ecbe86f928d9c5c0bf2cb6065c5672708511fc4
prerequisite-patch-id: ac68e9b65d3aa5661bd7ac0f2e77dcf5c75fcfe0
prerequisite-patch-id: e70f0f0c4d430d416a3ad848b37ffad16bd7df07
prerequisite-patch-id: 05adcddbc50f745ee2640b8c21f3bf76a217f249
prerequisite-patch-id: 0b60b7ab6196076f327c919be5e7b8bb2051ee12
Best regards,
--
James Hilliard <james.hilliard1@gmail.com>
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 62+ messages in thread
* [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 2:00 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 02/58] mtd: acquire an MTD handle from a referenced class device James Hilliard
` (56 subsequent siblings)
57 siblings, 1 reply; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Srinivas Kandagatla, Tze Yee Ng
An NVMEM consumer or device reference can outlive provider unbind. The
reference retains the NVMEM allocation, but not the private data or
hardware resources that the provider releases during remove. Pinning the
provider module also does not prevent unbind from freeing that state.
Reject new callback access once unregister starts and drain admitted
accesses with SRCU before returning to the provider. Cover register
access, keepout handling, cell post-processing and device matching.
Existing consumers retain core-owned metadata and return -ENODEV after
disconnection. Release the write-protect GPIO after the drain so
retained references do not prevent a new provider from requesting it.
The final device release frees only core-owned data.
Give cell entries their own references and retire layout cells when
their driver is unbound. Remove and drain cell attributes and callbacks
before layout-owned state is freed. Rebinding creates new entries; held
old cells remain disconnected. Remove partially added cells when a
layout probe fails, and destroy the layout during provider unregister
rather than at the final consumer put.
Preserve the existing exporter-module references. Providers remain
responsible for unregistering before releasing callback state.
Retain the firmware node explicitly and serialize consumer-reference
acquisition with disconnection. This lets MTD unregister an OTP provider
before freeing its backing object without requiring a release callback
into the departing provider module.
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/Kconfig | 1 +
drivers/nvmem/core.c | 297 +++++++++++++++++++++++++++--------------
drivers/nvmem/internals.h | 13 +-
drivers/nvmem/layouts.c | 2 +
include/linux/nvmem-provider.h | 3 +
5 files changed, 206 insertions(+), 110 deletions(-)
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index c36c2a4c2a0b..550f3118071a 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig NVMEM
bool "NVMEM Support"
+ select SRCU
imply NVMEM_LAYOUTS
help
Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0556d140170a..8571d821591c 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -27,6 +27,9 @@
#define FLAG_COMPAT BIT(0)
struct nvmem_cell_entry {
+ struct kref refcnt;
+ bool disconnected;
+ struct nvmem_layout *layout;
const char *name;
int offset;
size_t raw_len;
@@ -57,28 +60,25 @@ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
-
- if (!ops->reg_read)
+ if (!nvmem->reg_read)
return -EOPNOTSUPP;
- return ops->reg_read(nvmem->priv, offset, val, bytes);
+ return nvmem->reg_read(nvmem->priv, offset, val, bytes);
}
static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
int ret, wr_ok;
- if (!ops->reg_write)
+ if (!nvmem->reg_write)
return -EOPNOTSUPP;
ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 0);
if (ret)
return ret;
- wr_ok = ops->reg_write(nvmem->priv, offset, val, bytes);
+ wr_ok = nvmem->reg_write(nvmem->priv, offset, val, bytes);
ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 1);
if (ret)
@@ -154,6 +154,10 @@ static int nvmem_access_with_keepouts(struct nvmem_device *nvmem,
static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected))
+ return -ENODEV;
+
if (!nvmem->nkeepout)
return __nvmem_reg_read(nvmem, offset, val, bytes);
@@ -163,6 +167,10 @@ static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected))
+ return -ENODEV;
+
if (!nvmem->nkeepout)
return __nvmem_reg_write(nvmem, offset, val, bytes);
@@ -289,8 +297,6 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,
static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem)
{
- struct nvmem_operations *ops = nvmem->ops;
-
umode_t mode = 0400;
if (!nvmem->root_only)
@@ -299,10 +305,10 @@ static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem)
if (!nvmem->read_only)
mode |= 0200;
- if (!ops->reg_write)
+ if (!nvmem->reg_write)
mode &= ~0200;
- if (!ops->reg_read)
+ if (!nvmem->reg_read)
mode &= ~0444;
return mode;
@@ -333,7 +339,6 @@ static umode_t nvmem_attr_is_visible(struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct nvmem_device *nvmem = to_nvmem_device(dev);
- struct nvmem_operations *ops = nvmem->ops;
/*
* If the device has no .reg_write operation, do not allow
@@ -342,7 +347,7 @@ static umode_t nvmem_attr_is_visible(struct kobject *kobj,
* can be forced into read-write mode using the 'force_ro'
* attribute.
*/
- if (attr == &dev_attr_force_ro.attr && !ops->reg_write)
+ if (attr == &dev_attr_force_ro.attr && !nvmem->reg_write)
return 0; /* Attribute not visible */
return attr->mode;
@@ -468,9 +473,6 @@ static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem)
static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
{
- struct attribute_group group = {
- .name = "cells",
- };
struct nvmem_cell_entry *entry;
const struct bin_attribute **pattrs;
struct bin_attribute *attrs;
@@ -479,6 +481,9 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
guard(mutex)(&nvmem_mutex);
+ if (nvmem->disconnected)
+ return -ENODEV;
+
if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated)
return 0;
@@ -511,9 +516,10 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
i++;
}
- group.bin_attrs = pattrs;
+ nvmem->cells_group.name = "cells";
+ nvmem->cells_group.bin_attrs = pattrs;
- ret = device_add_group(&nvmem->dev, &group);
+ ret = device_add_group(&nvmem->dev, &nvmem->cells_group);
if (ret)
return ret;
@@ -540,8 +546,8 @@ static void nvmem_release(struct device *dev)
struct nvmem_device *nvmem = to_nvmem_device(dev);
ida_free(&nvmem_ida, nvmem->id);
- gpiod_put(nvmem->wp_gpio);
- kfree(nvmem->ops);
+ of_node_put(nvmem->dev.of_node);
+ cleanup_srcu_struct(&nvmem->srcu);
kfree(nvmem);
}
@@ -553,22 +559,30 @@ static const struct bus_type nvmem_bus_type = {
.name = "nvmem",
};
-static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
+static void nvmem_cell_entry_release(struct kref *ref)
{
- blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
- scoped_guard(mutex, &nvmem_mutex)
- list_del(&cell->node);
+ struct nvmem_cell_entry *cell = container_of(ref, struct nvmem_cell_entry, refcnt);
+
of_node_put(cell->np);
kfree_const(cell->name);
kfree(cell);
}
+static void nvmem_cell_entry_put(struct nvmem_cell_entry *cell)
+{
+ kref_put(&cell->refcnt, nvmem_cell_entry_release);
+}
+
static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
{
struct nvmem_cell_entry *cell, *p;
- list_for_each_entry_safe(cell, p, &nvmem->cells, node)
- nvmem_cell_entry_drop(cell);
+ list_for_each_entry_safe(cell, p, &nvmem->cells, node) {
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
+ scoped_guard(mutex, &nvmem_mutex)
+ list_del(&cell->node);
+ nvmem_cell_entry_put(cell);
+ }
}
static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
@@ -583,6 +597,7 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
struct nvmem_cell_entry *cell)
{
cell->nvmem = nvmem;
+ cell->disconnected = false;
cell->offset = info->offset;
cell->raw_len = info->raw_len ?: info->bytes;
cell->bytes = info->bytes;
@@ -653,6 +668,10 @@ int nvmem_add_one_cell(struct nvmem_device *nvmem,
struct nvmem_cell_entry *cell;
int rval;
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected))
+ return -ENODEV;
+
cell = kzalloc_obj(*cell);
if (!cell)
return -ENOMEM;
@@ -663,6 +682,8 @@ int nvmem_add_one_cell(struct nvmem_device *nvmem,
return rval;
}
+ kref_init(&cell->refcnt);
+ cell->layout = nvmem->layout;
nvmem_cell_entry_add(cell);
return 0;
@@ -729,6 +750,7 @@ nvmem_find_cell_entry_by_name(struct nvmem_device *nvmem, const char *cell_id)
list_for_each_entry(iter, &nvmem->cells, node) {
if (strcmp(cell_id, iter->name) == 0) {
cell = iter;
+ kref_get(&cell->refcnt);
break;
}
}
@@ -795,6 +817,10 @@ int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
const __be32 *addr;
int len, ret;
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected))
+ return -ENODEV;
+
for_each_child_of_node_scoped(np, child) {
struct nvmem_cell_info info = {0};
@@ -851,29 +877,81 @@ int nvmem_layout_register(struct nvmem_layout *layout)
if (!layout->add_cells)
return -EINVAL;
- /* Populate the cells */
- ret = layout->add_cells(layout);
+ scoped_guard(srcu, &layout->nvmem->srcu) {
+ if (READ_ONCE(layout->nvmem->disconnected))
+ return -ENODEV;
+ ret = layout->add_cells(layout);
+ }
if (ret)
- return ret;
+ goto err_unregister;
#ifdef CONFIG_NVMEM_SYSFS
ret = nvmem_populate_sysfs_cells(layout->nvmem);
- if (ret) {
- nvmem_device_remove_all_cells(layout->nvmem);
- return ret;
- }
+ if (ret)
+ goto err_unregister;
#endif
return 0;
+
+err_unregister:
+ nvmem_layout_unregister(layout);
+ return ret;
}
EXPORT_SYMBOL_GPL(nvmem_layout_register);
void nvmem_layout_unregister(struct nvmem_layout *layout)
{
- /* Keep the API even with an empty stub in case we need it later */
+ struct nvmem_device *nvmem = layout->nvmem;
+ struct nvmem_cell_entry *cell, *next;
+ struct attribute_group group = {};
+ LIST_HEAD(cells);
+
+ scoped_guard(mutex, &nvmem_mutex) {
+ if (nvmem->sysfs_cells_populated)
+ group = nvmem->cells_group;
+ list_for_each_entry_safe(cell, next, &nvmem->cells, node) {
+ if (cell->layout != layout)
+ continue;
+ WRITE_ONCE(cell->disconnected, true);
+ list_move_tail(&cell->node, &cells);
+ }
+ }
+
+ if (group.bin_attrs) {
+ device_remove_group(&nvmem->dev, &group);
+ scoped_guard(mutex, &nvmem_mutex) {
+ nvmem->sysfs_cells_populated = false;
+ memset(&nvmem->cells_group, 0, sizeof(nvmem->cells_group));
+ }
+ for (int i = 0; group.bin_attrs[i]; i++)
+ devm_kfree(&nvmem->dev, (void *)group.bin_attrs[i]->attr.name);
+ devm_kfree(&nvmem->dev, (void *)group.bin_attrs[0]);
+ devm_kfree(&nvmem->dev, group.bin_attrs);
+ }
+
+ /* Old cell handles retain data, never a departing layout's callbacks. */
+ synchronize_srcu(&nvmem->srcu);
+ list_for_each_entry_safe(cell, next, &cells, node) {
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
+ list_del(&cell->node);
+ nvmem_cell_entry_put(cell);
+ }
+#ifdef CONFIG_NVMEM_SYSFS
+ if (!READ_ONCE(nvmem->disconnected) && nvmem_populate_sysfs_cells(nvmem))
+ dev_warn(&nvmem->dev, "Failed to restore provider cell attributes\n");
+#endif
}
EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+static void nvmem_disconnect(struct nvmem_device *nvmem)
+{
+ scoped_guard(mutex, &nvmem_mutex)
+ WRITE_ONCE(nvmem->disconnected, true);
+ synchronize_srcu(&nvmem->srcu);
+ gpiod_put(nvmem->wp_gpio);
+ nvmem->wp_gpio = NULL;
+}
+
/**
* nvmem_register() - Register a nvmem device for given nvmem_config.
* Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem
@@ -883,10 +961,8 @@ EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
* Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device
* on success.
*/
-
struct nvmem_device *nvmem_register(const struct nvmem_config *config)
{
- struct nvmem_operations *ops;
struct nvmem_device *nvmem;
int rval;
@@ -900,15 +976,15 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (!nvmem)
return ERR_PTR(-ENOMEM);
- ops = kzalloc_obj(*ops);
- if (!ops) {
+ rval = init_srcu_struct(&nvmem->srcu);
+ if (rval) {
kfree(nvmem);
- return ERR_PTR(-ENOMEM);
+ return ERR_PTR(rval);
}
rval = ida_alloc(&nvmem_ida, GFP_KERNEL);
if (rval < 0) {
- kfree(ops);
+ cleanup_srcu_struct(&nvmem->srcu);
kfree(nvmem);
return ERR_PTR(rval);
}
@@ -918,10 +994,11 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->dev.type = &nvmem_provider_type;
nvmem->dev.bus = &nvmem_bus_type;
nvmem->dev.parent = config->dev;
- nvmem->ops = ops;
device_initialize(&nvmem->dev);
+ nvmem->priv = config->priv;
+
if (!config->ignore_wp)
nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp",
GPIOD_OUT_HIGH);
@@ -935,8 +1012,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
INIT_LIST_HEAD(&nvmem->cells);
nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
- ops->reg_read = config->reg_read;
- ops->reg_write = config->reg_write;
+ nvmem->reg_read = config->reg_read;
+ nvmem->reg_write = config->reg_write;
nvmem->owner = config->owner;
if (!nvmem->owner && config->dev->driver)
@@ -945,14 +1022,13 @@ 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;
if (config->of_node)
- nvmem->dev.of_node = config->of_node;
+ nvmem->dev.of_node = of_node_get(config->of_node);
else
- nvmem->dev.of_node = config->dev->of_node;
+ nvmem->dev.of_node = of_node_get(config->dev->of_node);
switch (config->id) {
case NVMEM_DEVID_NONE:
@@ -972,7 +1048,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
goto err_put_device;
nvmem->read_only = device_property_present(config->dev, "read-only") ||
- config->read_only || !ops->reg_write;
+ config->read_only || !nvmem->reg_write;
#ifdef CONFIG_NVMEM_SYSFS
nvmem->dev.groups = nvmem_dev_groups;
@@ -1010,7 +1086,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
rval = nvmem_populate_layout(nvmem);
if (rval)
- goto err_remove_dev;
+ goto err_unregister;
/* If the device has WP GPIO, default to read-only */
if (nvmem->wp_gpio)
@@ -1019,23 +1095,24 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
#ifdef CONFIG_NVMEM_SYSFS
rval = nvmem_populate_sysfs_cells(nvmem);
if (rval)
- goto err_destroy_layout;
+ goto err_unregister;
#endif
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
return nvmem;
-#ifdef CONFIG_NVMEM_SYSFS
-err_destroy_layout:
- nvmem_destroy_layout(nvmem);
-#endif
-err_remove_dev:
- device_del(&nvmem->dev);
+err_unregister:
+ nvmem_unregister(nvmem);
+ return ERR_PTR(rval);
err_remove_cells:
+ nvmem_disconnect(nvmem);
nvmem_device_remove_all_cells(nvmem);
nvmem_sysfs_remove_compat(nvmem);
+ goto err_put_disconnected;
err_put_device:
+ nvmem_disconnect(nvmem);
+err_put_disconnected:
put_device(&nvmem->dev);
return ERR_PTR(rval);
@@ -1048,24 +1125,29 @@ static void nvmem_device_release(struct kref *kref)
nvmem = container_of(kref, struct nvmem_device, refcnt);
- blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
-
- nvmem_sysfs_remove_compat(nvmem);
-
- nvmem_device_remove_all_cells(nvmem);
- nvmem_destroy_layout(nvmem);
- device_unregister(&nvmem->dev);
+ put_device(&nvmem->dev);
}
/**
* nvmem_unregister() - Unregister previously registered nvmem device
*
* @nvmem: Pointer to previously registered nvmem device.
+ *
+ * Stop and drain provider callbacks before returning. Existing consumers keep
+ * their core-owned metadata, but further access returns -ENODEV.
*/
void nvmem_unregister(struct nvmem_device *nvmem)
{
- if (nvmem)
- kref_put(&nvmem->refcnt, nvmem_device_release);
+ if (!nvmem)
+ return;
+
+ nvmem_disconnect(nvmem);
+ nvmem_destroy_layout(nvmem);
+ blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
+ nvmem_sysfs_remove_compat(nvmem);
+ device_del(&nvmem->dev);
+ nvmem_device_remove_all_cells(nvmem);
+ kref_put(&nvmem->refcnt, nvmem_device_release);
}
EXPORT_SYMBOL_GPL(nvmem_unregister);
@@ -1103,26 +1185,46 @@ struct nvmem_device *devm_nvmem_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_nvmem_register);
+struct nvmem_match_data {
+ void *data;
+ int (*match)(struct device *dev, const void *data);
+};
+
+static int nvmem_match_device(struct device *dev, const void *data)
+{
+ const struct nvmem_match_data *match = data;
+ struct nvmem_device *nvmem = to_nvmem_device(dev);
+
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected))
+ return 0;
+ return match->match(dev, match->data);
+}
+
static struct nvmem_device *nvmem_device_match(void *data,
int (*match)(struct device *dev, const void *data))
{
+ struct nvmem_match_data match_data = { .data = data, .match = match };
struct nvmem_device *nvmem = NULL;
struct device *dev;
- scoped_guard(mutex, &nvmem_mutex) {
- dev = bus_find_device(&nvmem_bus_type, NULL, data, match);
- if (dev)
- nvmem = to_nvmem_device(dev);
- }
+ guard(mutex)(&nvmem_mutex);
+ dev = bus_find_device(&nvmem_bus_type, NULL, &match_data, nvmem_match_device);
+ if (dev)
+ nvmem = to_nvmem_device(dev);
if (!nvmem)
return ERR_PTR(-EPROBE_DEFER);
+ if (READ_ONCE(nvmem->disconnected)) {
+ put_device(dev);
+ return ERR_PTR(-ENODEV);
+ }
+
if (!try_module_get(nvmem->owner)) {
dev_err(&nvmem->dev,
"could not increase module refcount for cell %s\n",
nvmem_dev_name(nvmem));
-
- put_device(&nvmem->dev);
+ put_device(dev);
return ERR_PTR(-EINVAL);
}
@@ -1337,8 +1439,10 @@ nvmem_cell_get_from_lookup(struct device *dev, const char *con_id)
cell = ERR_PTR(-ENOENT);
} else {
cell = nvmem_create_cell(cell_entry, con_id, 0);
- if (IS_ERR(cell))
+ if (IS_ERR(cell)) {
+ nvmem_cell_entry_put(cell_entry);
nvmem_device_put(nvmem);
+ }
}
break;
}
@@ -1347,12 +1451,6 @@ nvmem_cell_get_from_lookup(struct device *dev, const char *con_id)
return cell;
}
-static void nvmem_layout_module_put(struct nvmem_device *nvmem)
-{
- if (nvmem->layout && nvmem->layout->dev.driver)
- module_put(nvmem->layout->dev.driver->owner);
-}
-
#if IS_ENABLED(CONFIG_OF)
static struct nvmem_cell_entry *
nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np)
@@ -1362,25 +1460,15 @@ nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np
guard(mutex)(&nvmem_mutex);
list_for_each_entry(cell, &nvmem->cells, node) {
- if (np == cell->np)
+ if (np == cell->np) {
+ kref_get(&cell->refcnt);
return cell;
+ }
}
return NULL;
}
-static int nvmem_layout_module_get_optional(struct nvmem_device *nvmem)
-{
- if (!nvmem->layout)
- return 0;
-
- if (!nvmem->layout->dev.driver ||
- !try_module_get(nvmem->layout->dev.driver->owner))
- return -EPROBE_DEFER;
-
- return 0;
-}
-
/**
* of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
*
@@ -1443,25 +1531,17 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
return ERR_CAST(nvmem);
}
- ret = nvmem_layout_module_get_optional(nvmem);
- if (ret) {
- of_node_put(cell_np);
- nvmem_device_put(nvmem);
- return ERR_PTR(ret);
- }
-
cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np);
of_node_put(cell_np);
if (!cell_entry) {
- nvmem_layout_module_put(nvmem);
- ret = nvmem->layout ? -EPROBE_DEFER : -ENOENT;
+ ret = READ_ONCE(nvmem->layout) ? -EPROBE_DEFER : -ENOENT;
nvmem_device_put(nvmem);
return ERR_PTR(ret);
}
cell = nvmem_create_cell(cell_entry, id, cell_index);
if (IS_ERR(cell)) {
- nvmem_layout_module_put(nvmem);
+ nvmem_cell_entry_put(cell_entry);
nvmem_device_put(nvmem);
}
@@ -1575,8 +1655,8 @@ void nvmem_cell_put(struct nvmem_cell *cell)
if (cell->id)
kfree_const(cell->id);
+ nvmem_cell_entry_put(cell->entry);
kfree(cell);
- nvmem_layout_module_put(nvmem);
nvmem_device_put(nvmem);
}
EXPORT_SYMBOL_GPL(nvmem_cell_put);
@@ -1628,6 +1708,10 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
{
int rc;
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected) || READ_ONCE(cell->disconnected))
+ return -ENODEV;
+
rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len);
if (rc)
@@ -1742,7 +1826,14 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si
struct nvmem_device *nvmem = cell->nvmem;
int rc;
- if (!nvmem || nvmem->read_only ||
+ if (!nvmem)
+ return -EINVAL;
+
+ guard(srcu)(&nvmem->srcu);
+ if (READ_ONCE(nvmem->disconnected) || READ_ONCE(cell->disconnected))
+ return -ENODEV;
+
+ if (nvmem->read_only ||
(cell->bit_offset == 0 && len != cell->bytes))
return -EINVAL;
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4e610deeaa7b..4e15699d381b 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -6,12 +6,7 @@
#include <linux/device.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
-
-/* Hold pointers to callbacks owned by the nvmem provider module. */
-struct nvmem_operations {
- nvmem_reg_read_t reg_read;
- nvmem_reg_write_t reg_write;
-};
+#include <linux/srcu.h>
struct nvmem_device {
struct module *owner;
@@ -20,6 +15,8 @@ struct nvmem_device {
int word_size;
int id;
struct kref refcnt;
+ struct srcu_struct srcu;
+ bool disconnected;
size_t size;
bool read_only;
bool root_only;
@@ -34,9 +31,11 @@ struct nvmem_device {
unsigned int nkeepout;
struct gpio_desc *wp_gpio;
struct nvmem_layout *layout;
- struct nvmem_operations *ops;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
void *priv;
bool sysfs_cells_populated;
+ struct attribute_group cells_group;
};
int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np);
diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c
index 07a34be9669c..32995c9be68d 100644
--- a/drivers/nvmem/layouts.c
+++ b/drivers/nvmem/layouts.c
@@ -118,6 +118,7 @@ static int nvmem_layout_create_device(struct nvmem_device *nvmem,
ret = device_add(dev);
if (ret) {
+ nvmem->layout = NULL;
put_device(dev);
return ret;
}
@@ -192,6 +193,7 @@ void nvmem_destroy_layout(struct nvmem_device *nvmem)
dev = &nvmem->layout->dev;
of_node_clear_flag(dev->of_node, OF_POPULATED_BUS);
device_unregister(dev);
+ WRITE_ONCE(nvmem->layout, NULL);
}
int nvmem_layout_bus_register(void)
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 6063fe5b7784..dcf4c52b62c6 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -107,6 +107,9 @@ struct nvmem_cell_info {
*
* Note: Specifying name and setting id to -1 implies a unique device
* whose name is provided as-is (kept unaltered).
+ *
+ * Provider callbacks and @priv must remain valid until nvmem_unregister()
+ * returns, or until a failed nvmem_register() returns.
*/
struct nvmem_config {
struct device *dev;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 02/58] mtd: acquire an MTD handle from a referenced class device
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 03/58] mtd: separate class devices from retained backing objects James Hilliard
` (55 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Richard Cochran, Jonathan Lemon, Vadim Fedorenko
PTP OCP locates flash through the device hierarchy and uses driver data
directly. A class-device reference does not keep an MTD registered or own
the backing object needed by an erase or write.
Look up the exact device under mtd_table_mutex and acquire an ordinary MTD
handle before returning it. Use that handle for the OCP update and release
it on every exit. A removed device cannot be confused with a replacement
that reuses its name or number.
Cc: Richard Cochran <richardcochran@gmail.com>
Cc: Jonathan Lemon <jonathan.lemon@gmail.com>
Cc: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/mtdcore.c | 23 +++++++++++++++++++++++
drivers/ptp/ptp_ocp.c | 7 ++++++-
include/linux/mtd/mtd.h | 1 +
3 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 30b16f135f07..620326279957 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1329,6 +1329,29 @@ struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
EXPORT_SYMBOL_GPL(get_mtd_device);
+/**
+ * get_mtd_device_by_dev - acquire the MTD associated with a class device
+ * @dev: referenced MTD class device
+ *
+ * Return: a referenced MTD, or an error if the device has been removed.
+ */
+struct mtd_info *get_mtd_device_by_dev(struct device *dev)
+{
+ struct mtd_info *mtd;
+ int err;
+
+ guard(mutex)(&mtd_table_mutex);
+ mtd_for_each_device(mtd) {
+ if (&mtd->dev != dev)
+ continue;
+ err = __get_mtd_device(mtd);
+ return err ? ERR_PTR(err) : mtd;
+ }
+ return ERR_PTR(-ENODEV);
+}
+EXPORT_SYMBOL_GPL(get_mtd_device_by_dev);
+
+
int __get_mtd_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index 3d26ec1f7b9e..459aeb24ae6b 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -2083,7 +2083,7 @@ static int
ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev,
const struct firmware *fw)
{
- struct mtd_info *mtd = dev_get_drvdata(dev);
+ struct mtd_info *mtd;
struct ptp_ocp *bp = devlink_priv(devlink);
size_t off, len, size, resid, wrote;
struct erase_info erase;
@@ -2091,6 +2091,10 @@ ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev,
const u8 *data;
int err;
+ mtd = get_mtd_device_by_dev(dev);
+ if (IS_ERR(mtd))
+ return PTR_ERR(mtd);
+
err = ptp_ocp_devlink_fw_image(devlink, fw, &data, &size);
if (err)
goto out;
@@ -2120,6 +2124,7 @@ ptp_ocp_devlink_flash(struct devlink *devlink, struct device *dev,
resid -= len;
}
out:
+ put_mtd_device(mtd);
return err;
}
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index cd7b4671c456..745655e0d462 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -684,6 +684,7 @@ extern int mtd_device_parse_register(struct mtd_info *mtd,
mtd_device_parse_register(master, NULL, NULL, parts, nr_parts)
extern int mtd_device_unregister(struct mtd_info *master);
extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
+struct mtd_info *get_mtd_device_by_dev(struct device *dev);
extern int __get_mtd_device(struct mtd_info *mtd);
extern void __put_mtd_device(struct mtd_info *mtd);
extern struct mtd_info *of_get_mtd_device_by_node(struct device_node *np);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 03/58] mtd: separate class devices from retained backing objects
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns James Hilliard
2026-11-12 1:27 ` [PATCH v3 02/58] mtd: acquire an MTD handle from a referenced class device James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 2:48 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 04/58] mtd: retain virtual concatenations across component removal James Hilliard
` (54 subsequent siblings)
57 siblings, 1 reply; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Srinivas Kandagatla, Tze Yee Ng
MTD handles retain registered nodes, but not the backing allocation
owned by the controller driver. An open partition can therefore refer to
freed master metadata after unbind. Bare device references introduce
another lifetime: their final put may run after both the driver and MTD
modules have unloaded.
Allocate class devices separately from mtd_info. Delete their
interfaces, drain NVMEM access and clear module-owned pointers before
dropping MTD metadata. Keep the class object, device type, PM dispatch
and final device-data release in a small built-in component, including
when MTD is modular. A bare device reference then retains data without
requiring a callback into an unloaded module.
The PM core can retain a class device across removal. Clear its backing
association under the device lock, which also covers PM callback lookup
and execution, so active callbacks finish before metadata is released.
Later callbacks find no backing object and return without accessing it.
Keep the attribute groups on the device and detach them after
device_del() has drained their callbacks.
Add optional backing ownership initialized by mtd_set_release(). The
driver, registered nodes and acquired MTD handles each hold a reference.
Retained nested partitions also own references to their parent metadata.
Drop the driver's reference with mtd_put_master() after unregister and
hardware disconnection; final metadata cleanup must not access released
resources.
Finish driver cleanup before dropping its initial reference. Final
release belongs to the subsystem core providing the MTD operations and
only frees retained data. The existing MTD handle ownership keeps that
core resident; it does not require keeping the controller driver loaded
or waiting for userspace to close files during module removal.
Distinguish registration from retained references, rejecting new opens
of removed devices. Balance direct master opens even with
partitioned-master support disabled, independently of later publication.
Remove the IDR entry at the final MTD put, when the corresponding NVMEM
name is unregistered.
Update MTD users for the separate class device, parent and firmware-node
fields. Registration failures detach the device before returning backing
storage to the caller, including when device release is deferred.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
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/Makefile | 2 +-
drivers/media/pci/cobalt/cobalt-flash.c | 2 +-
drivers/mtd/Kconfig | 4 +
drivers/mtd/Makefile | 1 +
drivers/mtd/devices/bcm47xxsflash.c | 2 +-
drivers/mtd/devices/docg3.c | 4 +-
drivers/mtd/devices/mchp23k256.c | 2 +-
drivers/mtd/devices/mchp48l640.c | 2 +-
drivers/mtd/devices/mtd_dataflash.c | 2 +-
drivers/mtd/devices/mtd_intel_dg.c | 30 +--
drivers/mtd/devices/powernv_flash.c | 4 +-
drivers/mtd/devices/spear_smi.c | 2 +-
drivers/mtd/devices/sst25l.c | 2 +-
drivers/mtd/devices/st_spi_fsm.c | 8 +-
drivers/mtd/hyperbus/hyperbus-core.c | 2 +-
drivers/mtd/lpddr/lpddr2_nvm.c | 2 +-
drivers/mtd/maps/lantiq-flash.c | 2 +-
drivers/mtd/maps/physmap-core.c | 2 +-
drivers/mtd/maps/plat-ram.c | 2 +-
drivers/mtd/maps/pxa2xx-flash.c | 2 +-
drivers/mtd/maps/sa1100-flash.c | 2 +-
drivers/mtd/maps/vmu-flash.c | 2 +-
drivers/mtd/mtd_blkdevs.c | 2 +-
drivers/mtd/mtd_virt_concat.c | 10 +-
drivers/mtd/mtdcore.c | 282 ++++++++++++---------
drivers/mtd/mtdcore.h | 5 +
drivers/mtd/mtddevice.c | 59 +++++
drivers/mtd/mtddevice.h | 12 +
drivers/mtd/mtdpart.c | 14 +-
drivers/mtd/mtdpstore.c | 50 ++--
drivers/mtd/nand/ecc.c | 3 +-
drivers/mtd/nand/onenand/generic.c | 2 +-
drivers/mtd/nand/onenand/onenand_omap2.c | 2 +-
drivers/mtd/nand/onenand/onenand_samsung.c | 2 +-
drivers/mtd/nand/raw/ams-delta.c | 4 +-
drivers/mtd/nand/raw/arasan-nand-controller.c | 2 +-
drivers/mtd/nand/raw/atmel/nand-controller.c | 2 +-
drivers/mtd/nand/raw/au1550nd.c | 2 +-
drivers/mtd/nand/raw/bcm47xxnflash/main.c | 2 +-
drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 +-
drivers/mtd/nand/raw/cadence-nand-controller.c | 2 +-
drivers/mtd/nand/raw/cafe_nand.c | 2 +-
drivers/mtd/nand/raw/davinci_nand.c | 2 +-
drivers/mtd/nand/raw/denali.c | 2 +-
drivers/mtd/nand/raw/fsl_elbc_nand.c | 2 +-
drivers/mtd/nand/raw/fsl_ifc_nand.c | 2 +-
drivers/mtd/nand/raw/fsl_upm.c | 2 +-
drivers/mtd/nand/raw/fsmc_nand.c | 2 +-
drivers/mtd/nand/raw/gpio.c | 2 +-
drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 2 +-
drivers/mtd/nand/raw/hisi504_nand.c | 2 +-
drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c | 2 +-
drivers/mtd/nand/raw/intel-nand-controller.c | 2 +-
drivers/mtd/nand/raw/loongson-nand-controller.c | 2 +-
drivers/mtd/nand/raw/lpc32xx_mlc.c | 18 +-
drivers/mtd/nand/raw/lpc32xx_slc.c | 18 +-
drivers/mtd/nand/raw/marvell_nand.c | 2 +-
drivers/mtd/nand/raw/meson_nand.c | 2 +-
drivers/mtd/nand/raw/mpc5121_nfc.c | 2 +-
drivers/mtd/nand/raw/mtk_nand.c | 4 +-
drivers/mtd/nand/raw/mxc_nand.c | 4 +-
drivers/mtd/nand/raw/mxic_nand.c | 2 +-
drivers/mtd/nand/raw/nand_base.c | 4 +-
drivers/mtd/nand/raw/nand_macronix.c | 4 +-
drivers/mtd/nand/raw/nand_onfi.c | 4 +-
drivers/mtd/nand/raw/ndfc.c | 2 +-
.../mtd/nand/raw/nuvoton-ma35d1-nand-controller.c | 6 +-
drivers/mtd/nand/raw/omap2.c | 2 +-
drivers/mtd/nand/raw/orion_nand.c | 2 +-
drivers/mtd/nand/raw/pasemi_nand.c | 2 +-
drivers/mtd/nand/raw/pl35x-nand-controller.c | 2 +-
drivers/mtd/nand/raw/plat_nand.c | 2 +-
drivers/mtd/nand/raw/qcom_nandc.c | 2 +-
drivers/mtd/nand/raw/r852.c | 8 +-
drivers/mtd/nand/raw/renesas-nand-controller.c | 2 +-
drivers/mtd/nand/raw/rockchip-nand-controller.c | 4 +-
drivers/mtd/nand/raw/sh_flctl.c | 2 +-
drivers/mtd/nand/raw/sharpsl.c | 2 +-
drivers/mtd/nand/raw/socrates_nand.c | 2 +-
drivers/mtd/nand/raw/stm32_fmc2_nand.c | 2 +-
drivers/mtd/nand/raw/sunxi_nand.c | 2 +-
drivers/mtd/nand/raw/technologic-nand-controller.c | 2 +-
drivers/mtd/nand/raw/tegra_nand.c | 2 +-
drivers/mtd/nand/raw/txx9ndfmc.c | 2 +-
drivers/mtd/nand/raw/vf610_nfc.c | 2 +-
drivers/mtd/nand/raw/xway_nand.c | 2 +-
drivers/mtd/nand/spi/core.c | 2 +-
drivers/mtd/nand/spi/esmt.c | 2 +-
drivers/mtd/nand/spi/micron.c | 4 +-
drivers/mtd/nand/spi/otp.c | 2 +-
drivers/mtd/spi-nor/core.c | 2 +-
drivers/mtd/ubi/build.c | 2 +-
include/linux/mtd/mtd.h | 20 +-
93 files changed, 435 insertions(+), 285 deletions(-)
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..86adb803c79a 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -84,7 +84,7 @@ obj-y += scsi/
obj-y += nvme/
obj-$(CONFIG_ATA) += ata/
obj-$(CONFIG_TARGET_CORE) += target/
-obj-$(CONFIG_MTD) += mtd/
+obj-$(CONFIG_MTD_DEVICE) += mtd/
obj-$(CONFIG_SPI) += spi/
obj-$(CONFIG_SPMI) += spmi/
obj-$(CONFIG_HSI) += hsi/
diff --git a/drivers/media/pci/cobalt/cobalt-flash.c b/drivers/media/pci/cobalt/cobalt-flash.c
index 1d3c64b4cf6d..14f87cd54b9c 100644
--- a/drivers/media/pci/cobalt/cobalt-flash.c
+++ b/drivers/media/pci/cobalt/cobalt-flash.c
@@ -102,7 +102,7 @@ int cobalt_flash_probe(struct cobalt *cobalt)
}
mtd->owner = THIS_MODULE;
- mtd->dev.parent = &cobalt->pci_dev->dev;
+ mtd->parent_dev = &cobalt->pci_dev->dev;
mtd_device_register(mtd, NULL, 0);
return 0;
}
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 0421c6208de7..d13a065874ba 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -1,5 +1,9 @@
+config MTD_DEVICE
+ bool
+
menuconfig MTD
tristate "Memory Technology Device (MTD) support"
+ select MTD_DEVICE
imply NVMEM
help
Memory Technology Devices are flash, RAM and similar chips, often
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 7b6dd53e8150..8bb19a6da358 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -4,6 +4,7 @@
#
# Core functionality.
+obj-$(CONFIG_MTD_DEVICE) += mtddevice.o
obj-$(CONFIG_MTD) += mtd.o
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o
diff --git a/drivers/mtd/devices/bcm47xxsflash.c b/drivers/mtd/devices/bcm47xxsflash.c
index 2edc46741774..bb18ed449c1c 100644
--- a/drivers/mtd/devices/bcm47xxsflash.c
+++ b/drivers/mtd/devices/bcm47xxsflash.c
@@ -252,7 +252,7 @@ static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s,
struct mtd_info *mtd = &b47s->mtd;
mtd->priv = b47s;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd->name = "bcm47xxsflash";
mtd->type = MTD_NORFLASH;
diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c
index 603fd0efc2ea..9e582457f8b5 100644
--- a/drivers/mtd/devices/docg3.c
+++ b/drivers/mtd/devices/docg3.c
@@ -1730,7 +1730,7 @@ static void __init doc_dbg_register(struct mtd_info *floor)
if (IS_ERR_OR_NULL(root)) {
if (IS_ENABLED(CONFIG_DEBUG_FS) &&
!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
- dev_warn(floor->dev.parent,
+ dev_warn(floor->parent_dev,
"CONFIG_MTD_PARTITIONED_MASTER must be enabled to expose debugfs stuff\n");
return;
}
@@ -1817,7 +1817,7 @@ doc_probe_device(struct docg3_cascade *cascade, int floor, struct device *dev)
if (!mtd)
goto nomem2;
mtd->priv = docg3;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
bbt_nbpages = DIV_ROUND_UP(docg3->max_block + 1,
8 * DOC_LAYOUT_PAGE_SIZE);
docg3->bbt = kcalloc(DOC_LAYOUT_PAGE_SIZE, bbt_nbpages, GFP_KERNEL);
diff --git a/drivers/mtd/devices/mchp23k256.c b/drivers/mtd/devices/mchp23k256.c
index 66e058df4c32..65975d426973 100644
--- a/drivers/mtd/devices/mchp23k256.c
+++ b/drivers/mtd/devices/mchp23k256.c
@@ -193,7 +193,7 @@ static int mchp23k256_probe(struct spi_device *spi)
flash->caps = &mchp23k256_caps;
mtd_set_of_node(&flash->mtd, spi->dev.of_node);
- flash->mtd.dev.parent = &spi->dev;
+ flash->mtd.parent_dev = &spi->dev;
flash->mtd.type = MTD_RAM;
flash->mtd.flags = MTD_CAP_RAM;
flash->mtd.writesize = 1;
diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c
index 4af9208f9690..1768a79024ea 100644
--- a/drivers/mtd/devices/mchp48l640.c
+++ b/drivers/mtd/devices/mchp48l640.c
@@ -340,7 +340,7 @@ static int mchp48l640_probe(struct spi_device *spi)
flash->caps = &mchp48l640_caps;
mtd_set_of_node(&flash->mtd, spi->dev.of_node);
- flash->mtd.dev.parent = &spi->dev;
+ flash->mtd.parent_dev = &spi->dev;
flash->mtd.type = MTD_RAM;
flash->mtd.flags = MTD_CAP_RAM;
flash->mtd.writesize = flash->caps->page_size;
diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c
index e766258d76f7..72158ee7518f 100644
--- a/drivers/mtd/devices/mtd_dataflash.c
+++ b/drivers/mtd/devices/mtd_dataflash.c
@@ -653,7 +653,7 @@ static int add_dataflash_otp(struct spi_device *spi, char *name, int nr_pages,
device->_write = dataflash_write;
device->priv = priv;
- device->dev.parent = &spi->dev;
+ device->parent_dev = &spi->dev;
mtd_set_of_node(device, spi->dev.of_node);
if (revision >= 'c')
diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c
index a566e86eb5e3..d4dc094fa170 100644
--- a/drivers/mtd/devices/mtd_intel_dg.c
+++ b/drivers/mtd/devices/mtd_intel_dg.c
@@ -510,7 +510,7 @@ static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
return -EINVAL;
if (!IS_ALIGNED(info->addr, SZ_4K) || !IS_ALIGNED(info->len, SZ_4K)) {
- dev_err(&mtd->dev, "unaligned erase %llx %llx\n",
+ dev_err(mtd->dev, "unaligned erase %llx %llx\n",
info->addr, info->len);
info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
return -EINVAL;
@@ -521,7 +521,7 @@ static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
ret = pm_runtime_resume_and_get(nvm->dev);
if (ret < 0) {
- dev_err(&mtd->dev, "rpm: get failed %d\n", ret);
+ dev_err(mtd->dev, "rpm: get failed %d\n", ret);
return ret;
}
@@ -530,7 +530,7 @@ static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
while (total_len > 0) {
if (!IS_ALIGNED(addr, SZ_4K) || !IS_ALIGNED(total_len, SZ_4K)) {
- dev_err(&mtd->dev, "unaligned erase %llx %zx\n", addr, total_len);
+ dev_err(mtd->dev, "unaligned erase %llx %zx\n", addr, total_len);
info->fail_addr = addr;
ret = -ERANGE;
break;
@@ -538,7 +538,7 @@ static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
idx = idg_nvm_get_region(nvm, addr);
if (idx >= nvm->nregions) {
- dev_err(&mtd->dev, "out of range");
+ dev_err(mtd->dev, "out of range");
info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
ret = -ERANGE;
break;
@@ -550,12 +550,12 @@ static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
if (len > nvm->regions[idx].size - from)
len = nvm->regions[idx].size - from;
- dev_dbg(&mtd->dev, "erasing region[%d] %s from %llx len %zx\n",
+ dev_dbg(mtd->dev, "erasing region[%d] %s from %llx len %zx\n",
region, nvm->regions[idx].name, from, len);
bytes = idg_erase(nvm, region, from, len, &info->fail_addr);
if (bytes < 0) {
- dev_dbg(&mtd->dev, "erase failed with %zd\n", bytes);
+ dev_dbg(mtd->dev, "erase failed with %zd\n", bytes);
info->fail_addr += nvm->regions[idx].offset;
ret = bytes;
break;
@@ -582,11 +582,11 @@ static int intel_dg_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
idx = idg_nvm_get_region(nvm, from);
- dev_dbg(&mtd->dev, "reading region[%d] %s from %lld len %zd\n",
+ dev_dbg(mtd->dev, "reading region[%d] %s from %lld len %zd\n",
nvm->regions[idx].id, nvm->regions[idx].name, from, len);
if (idx >= nvm->nregions) {
- dev_err(&mtd->dev, "out of range");
+ dev_err(mtd->dev, "out of range");
return -ERANGE;
}
@@ -597,7 +597,7 @@ static int intel_dg_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
ret = pm_runtime_resume_and_get(nvm->dev);
if (ret < 0) {
- dev_err(&mtd->dev, "rpm: get failed %zd\n", ret);
+ dev_err(mtd->dev, "rpm: get failed %zd\n", ret);
return ret;
}
@@ -605,7 +605,7 @@ static int intel_dg_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
ret = idg_read(nvm, region, from, len, buf);
if (ret < 0) {
- dev_dbg(&mtd->dev, "read failed with %zd\n", ret);
+ dev_dbg(mtd->dev, "read failed with %zd\n", ret);
} else {
*retlen = ret;
ret = 0;
@@ -628,11 +628,11 @@ static int intel_dg_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
idx = idg_nvm_get_region(nvm, to);
- dev_dbg(&mtd->dev, "writing region[%d] %s to %lld len %zd\n",
+ dev_dbg(mtd->dev, "writing region[%d] %s to %lld len %zd\n",
nvm->regions[idx].id, nvm->regions[idx].name, to, len);
if (idx >= nvm->nregions) {
- dev_err(&mtd->dev, "out of range");
+ dev_err(mtd->dev, "out of range");
return -ERANGE;
}
@@ -643,7 +643,7 @@ static int intel_dg_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
ret = pm_runtime_resume_and_get(nvm->dev);
if (ret < 0) {
- dev_err(&mtd->dev, "rpm: get failed %zd\n", ret);
+ dev_err(mtd->dev, "rpm: get failed %zd\n", ret);
return ret;
}
@@ -651,7 +651,7 @@ static int intel_dg_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
ret = idg_write(nvm, region, to, len, buf);
if (ret < 0) {
- dev_dbg(&mtd->dev, "write failed with %zd\n", ret);
+ dev_dbg(mtd->dev, "write failed with %zd\n", ret);
} else {
*retlen = ret;
ret = 0;
@@ -707,7 +707,7 @@ static int intel_dg_nvm_init_mtd(struct intel_dg_nvm *nvm, struct device *device
dev_dbg(device, "registering with mtd\n");
nvm->mtd.owner = THIS_MODULE;
- nvm->mtd.dev.parent = device;
+ nvm->mtd.parent_dev = device;
nvm->mtd.flags = MTD_CAP_NORFLASH;
nvm->mtd.type = MTD_DATAFLASH;
nvm->mtd.priv = nvm;
diff --git a/drivers/mtd/devices/powernv_flash.c b/drivers/mtd/devices/powernv_flash.c
index a12427d9e20f..b02e8695703e 100644
--- a/drivers/mtd/devices/powernv_flash.c
+++ b/drivers/mtd/devices/powernv_flash.c
@@ -47,7 +47,7 @@ static int powernv_flash_async_op(struct mtd_info *mtd, enum flash_op op,
loff_t offset, size_t len, size_t *retlen, u_char *buf)
{
struct powernv_flash *info = (struct powernv_flash *)mtd->priv;
- struct device *dev = &mtd->dev;
+ struct device *dev = mtd->dev;
int token;
struct opal_msg msg;
int rc;
@@ -219,7 +219,7 @@ static int powernv_flash_set_driver_info(struct device *dev,
mtd->_erase = powernv_flash_erase;
mtd->_read = powernv_flash_read;
mtd->_write = powernv_flash_write;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd_set_of_node(mtd, dev->of_node);
return 0;
}
diff --git a/drivers/mtd/devices/spear_smi.c b/drivers/mtd/devices/spear_smi.c
index f02f96bff450..d46610c52fae 100644
--- a/drivers/mtd/devices/spear_smi.c
+++ b/drivers/mtd/devices/spear_smi.c
@@ -885,7 +885,7 @@ static int spear_smi_setup_banks(struct platform_device *pdev,
else
flash->mtd.name = flash_devices[flash_index].name;
- flash->mtd.dev.parent = &pdev->dev;
+ flash->mtd.parent_dev = &pdev->dev;
mtd_set_of_node(&flash->mtd, np);
flash->mtd.type = MTD_NORFLASH;
flash->mtd.writesize = 1;
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c
index 8813994ce9f4..5d178dddab7b 100644
--- a/drivers/mtd/devices/sst25l.c
+++ b/drivers/mtd/devices/sst25l.c
@@ -368,7 +368,7 @@ static int sst25l_probe(struct spi_device *spi)
if (data && data->name)
flash->mtd.name = data->name;
- flash->mtd.dev.parent = &spi->dev;
+ flash->mtd.parent_dev = &spi->dev;
flash->mtd.type = MTD_NORFLASH;
flash->mtd.flags = MTD_CAP_NORFLASH;
flash->mtd.erasesize = flash_info->erase_size;
diff --git a/drivers/mtd/devices/st_spi_fsm.c b/drivers/mtd/devices/st_spi_fsm.c
index f2266145b821..83972b8ce30d 100644
--- a/drivers/mtd/devices/st_spi_fsm.c
+++ b/drivers/mtd/devices/st_spi_fsm.c
@@ -1669,7 +1669,7 @@ static int stfsm_write(struct stfsm *fsm, const uint8_t *buf,
static int stfsm_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
- struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent);
+ struct stfsm *fsm = dev_get_drvdata(mtd->parent_dev);
uint32_t bytes;
dev_dbg(fsm->dev, "%s from 0x%08x, len %zd\n",
@@ -1745,7 +1745,7 @@ static int stfsm_erase_chip(struct stfsm *fsm)
static int stfsm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf)
{
- struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent);
+ struct stfsm *fsm = dev_get_drvdata(mtd->parent_dev);
u32 page_offs;
u32 bytes;
@@ -1790,7 +1790,7 @@ static int stfsm_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
*/
static int stfsm_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
- struct stfsm *fsm = dev_get_drvdata(mtd->dev.parent);
+ struct stfsm *fsm = dev_get_drvdata(mtd->parent_dev);
u32 addr, len;
int ret;
@@ -2074,7 +2074,7 @@ static int stfsm_probe(struct platform_device *pdev)
return ret;
fsm->mtd.name = info->name;
- fsm->mtd.dev.parent = &pdev->dev;
+ fsm->mtd.parent_dev = &pdev->dev;
mtd_set_of_node(&fsm->mtd, np);
fsm->mtd.type = MTD_NORFLASH;
fsm->mtd.writesize = 4;
diff --git a/drivers/mtd/hyperbus/hyperbus-core.c b/drivers/mtd/hyperbus/hyperbus-core.c
index 4d8047d43e48..48cdf5c031af 100644
--- a/drivers/mtd/hyperbus/hyperbus-core.c
+++ b/drivers/mtd/hyperbus/hyperbus-core.c
@@ -112,7 +112,7 @@ int hyperbus_register_device(struct hyperbus_device *hbdev)
return -ENODEV;
}
- hbdev->mtd->dev.parent = dev;
+ hbdev->mtd->parent_dev = dev;
mtd_set_of_node(hbdev->mtd, np);
ret = mtd_device_register(hbdev->mtd, NULL, 0);
diff --git a/drivers/mtd/lpddr/lpddr2_nvm.c b/drivers/mtd/lpddr/lpddr2_nvm.c
index 565b71f7fdd5..447431187edf 100644
--- a/drivers/mtd/lpddr/lpddr2_nvm.c
+++ b/drivers/mtd/lpddr/lpddr2_nvm.c
@@ -457,7 +457,7 @@ static int lpddr2_nvm_probe(struct platform_device *pdev)
/* Populate mtd_info data structure */
*mtd = lpddr2_nvm_mtd_info;
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
mtd->name = pdev->dev.init_name;
mtd->priv = map;
mtd->size = resource_size(add_range);
diff --git a/drivers/mtd/maps/lantiq-flash.c b/drivers/mtd/maps/lantiq-flash.c
index 80d467eba92a..35b2c821ce3c 100644
--- a/drivers/mtd/maps/lantiq-flash.c
+++ b/drivers/mtd/maps/lantiq-flash.c
@@ -146,7 +146,7 @@ ltq_mtd_probe(struct platform_device *pdev)
return -ENXIO;
}
- ltq_mtd->mtd->dev.parent = &pdev->dev;
+ ltq_mtd->mtd->parent_dev = &pdev->dev;
mtd_set_of_node(ltq_mtd->mtd, pdev->dev.of_node);
cfi = ltq_mtd->map->fldrv_priv;
diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c
index dcda7685fc99..e2bb14931bc8 100644
--- a/drivers/mtd/maps/physmap-core.c
+++ b/drivers/mtd/maps/physmap-core.c
@@ -568,7 +568,7 @@ static int physmap_flash_probe(struct platform_device *dev)
err = -ENXIO;
goto err_out;
}
- info->mtds[i]->dev.parent = &dev->dev;
+ info->mtds[i]->parent_dev = &dev->dev;
}
if (info->nmaps == 1) {
diff --git a/drivers/mtd/maps/plat-ram.c b/drivers/mtd/maps/plat-ram.c
index 816111031cf1..5a7c9d1ade01 100644
--- a/drivers/mtd/maps/plat-ram.c
+++ b/drivers/mtd/maps/plat-ram.c
@@ -163,7 +163,7 @@ static int platram_probe(struct platform_device *pdev)
goto exit_free;
}
- info->mtd->dev.parent = &pdev->dev;
+ info->mtd->parent_dev = &pdev->dev;
platram_setrw(info, PLATRAM_RW);
diff --git a/drivers/mtd/maps/pxa2xx-flash.c b/drivers/mtd/maps/pxa2xx-flash.c
index bc303a9909d4..754419d19554 100644
--- a/drivers/mtd/maps/pxa2xx-flash.c
+++ b/drivers/mtd/maps/pxa2xx-flash.c
@@ -89,7 +89,7 @@ static int pxa2xx_flash_probe(struct platform_device *pdev)
kfree(info);
return -EIO;
}
- info->mtd->dev.parent = &pdev->dev;
+ info->mtd->parent_dev = &pdev->dev;
mtd_device_parse_register(info->mtd, probes, NULL, flash->parts,
flash->nr_parts);
diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c
index 4506b27a905e..dcddcbf18fdf 100644
--- a/drivers/mtd/maps/sa1100-flash.c
+++ b/drivers/mtd/maps/sa1100-flash.c
@@ -242,7 +242,7 @@ static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev,
goto err;
}
}
- info->mtd->dev.parent = &pdev->dev;
+ info->mtd->parent_dev = &pdev->dev;
if (ret == 0)
return info;
diff --git a/drivers/mtd/maps/vmu-flash.c b/drivers/mtd/maps/vmu-flash.c
index 10244e6731d0..82709a81c35c 100644
--- a/drivers/mtd/maps/vmu-flash.c
+++ b/drivers/mtd/maps/vmu-flash.c
@@ -547,7 +547,7 @@ static void vmu_queryblocks(struct mapleq *mq)
mpart->partition = card->partition;
mtd_cur->priv = mpart;
mtd_cur->owner = THIS_MODULE;
- mtd_cur->dev.parent = &mdev->dev;
+ mtd_cur->parent_dev = &mdev->dev;
pcache = kzalloc_obj(struct vmu_cache);
if (!pcache)
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 4d2e7b7774e9..957087c10220 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -379,7 +379,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
if (new->readonly)
set_disk_ro(gd, 1);
- ret = device_add_disk(&new->mtd->dev, gd, NULL);
+ ret = device_add_disk(new->mtd->dev, gd, NULL);
if (ret)
goto out_cleanup_disk;
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
index da4277ced4d6..0d501e1565c0 100644
--- a/drivers/mtd/mtd_virt_concat.c
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -100,7 +100,7 @@ int mtd_virt_concat_destroy(struct mtd_info *mtd)
/* Find the concat item that hold the mtd device */
for (idx = 0; idx < item->count; idx++) {
- if (item->nodes[idx] == mtd->dev.of_node) {
+ if (item->nodes[idx] == mtd_get_of_node(mtd)) {
is_mtd_found = true;
break;
}
@@ -116,7 +116,7 @@ int mtd_virt_concat_destroy(struct mtd_info *mtd)
*/
for (idx = 0; idx < concat->num_subdev; idx++) {
child = concat->subdev[idx];
- if (child->dev.of_node != mtd->dev.of_node) {
+ if (mtd_get_of_node(child) != mtd_get_of_node(mtd)) {
ret = add_mtd_device(child);
if (ret)
goto out;
@@ -223,7 +223,7 @@ bool mtd_virt_concat_add(struct mtd_info *mtd)
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) {
+ if (item->nodes[idx] == mtd_get_of_node(mtd)) {
concat->subdev[concat->num_subdev++] = mtd;
return true;
}
@@ -333,8 +333,8 @@ int mtd_virt_concat_create_join(void)
}
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;
+ concat->mtd.parent_dev = concat->subdev[0]->parent_dev;
+ mtd_set_of_node(&concat->mtd, mtd_get_of_node(concat->subdev[0]));
/* Add the mtd device */
ret = add_mtd_device(&concat->mtd);
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 620326279957..5793c865f3a2 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -37,38 +37,10 @@
#include <linux/mtd/concat.h>
#include "mtdcore.h"
+#include "mtddevice.h"
struct backing_dev_info *mtd_bdi;
-#ifdef CONFIG_PM_SLEEP
-
-static int mtd_cls_suspend(struct device *dev)
-{
- struct mtd_info *mtd = dev_get_drvdata(dev);
-
- return mtd ? mtd_suspend(mtd) : 0;
-}
-
-static int mtd_cls_resume(struct device *dev)
-{
- struct mtd_info *mtd = dev_get_drvdata(dev);
-
- if (mtd)
- mtd_resume(mtd);
- return 0;
-}
-
-static SIMPLE_DEV_PM_OPS(mtd_cls_pm_ops, mtd_cls_suspend, mtd_cls_resume);
-#define MTD_CLS_PM_OPS (&mtd_cls_pm_ops)
-#else
-#define MTD_CLS_PM_OPS NULL
-#endif
-
-static struct class mtd_class = {
- .name = "mtd",
- .pm = MTD_CLS_PM_OPS,
-};
-
static DEFINE_IDR(mtd_idr);
/* These are exported solely for the purpose of mtd_blkdevs.c. You
@@ -78,7 +50,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,54 +67,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);
+
+ master->master.release(master);
+}
+
+void mtd_get_lifetime(struct mtd_info *master)
+{
+ if (master->master.release)
+ kref_get(&master->master.life_refcnt);
+}
+
+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: core callback freeing the backing object and 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.
+ * Driver cleanup must finish before the initial reference is dropped. The
+ * release callback only frees data and belongs to the core providing the MTD
+ * operations, identified by master->owner, not to the controller driver.
*/
-static void mtd_release(struct device *dev)
+void mtd_set_release(struct mtd_info *master,
+ void (*release)(struct mtd_info *mtd))
{
- struct mtd_info *mtd = dev_get_drvdata(dev);
- dev_t index = MTD_DEVT(mtd->index);
+ 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 and driver callbacks must already be disconnected and
+ * drained. Unbind may return while handles retain core-owned metadata.
+ */
+void mtd_put_master(struct mtd_info *master)
+{
+ mtd_put_lifetime(master);
+}
+EXPORT_SYMBOL_GPL(mtd_put_master);
+
+static void mtd_data_release(struct kref *ref)
+{
+ struct mtd_info *mtd = container_of(ref, struct mtd_info, data_refcnt);
+ struct mtd_info *master = mtd->ref_master;
+ struct mtd_info *parent = mtd->parent;
+ struct device *dev = mtd->dev;
- idr_remove(&mtd_idr, mtd->index);
of_node_put(mtd_get_of_node(mtd));
+ mtd->dev = NULL;
+ mtd->ref_master = NULL;
if (mtd_is_partition(mtd))
release_mtd_partition(mtd);
- /* remove /dev/mtdXro node */
- device_destroy(&mtd_class, index + 1);
+ if (master) {
+ if (parent && parent != master)
+ mtd_put_data(parent);
+ mtd_put_lifetime(master);
+ }
+ put_device(dev);
+ module_put(THIS_MODULE);
}
-/*
- * No-op device release used in add_mtd_device() error paths.
- * 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)
+void mtd_get_data(struct mtd_info *mtd)
{
+ kref_get(&mtd->data_refcnt);
+}
+
+void mtd_put_data(struct mtd_info *mtd)
+{
+ kref_put(&mtd->data_refcnt, mtd_data_release);
+}
+
+static void mtd_device_detach(struct device *dev)
+{
+ /* A queued system-sleep callback may run after device_del(). */
+ guard(device)(dev);
+
+ dev_set_drvdata(dev, NULL);
+ dev->groups = NULL;
}
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);
+
+ /* 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);
/* Try to remove the NVMEM provider */
nvmem_unregister(mtd->nvmem);
- device_unregister(&mtd->dev);
-
- /*
- * Clear dev so mtd can be safely re-registered later if desired.
- * Should not be done for partition,
- * as it was already destroyed in device_unregister().
- */
- if (!is_partition)
- memset(&mtd->dev, 0, sizeof(mtd->dev));
-
- module_put(THIS_MODULE);
+ device_del(mtd->dev);
+ mtd_device_detach(mtd->dev);
+ mtd_put_data(mtd);
}
#define MTD_DEVICE_ATTR_RO(name) \
@@ -371,12 +414,6 @@ static struct attribute *mtd_attrs[] = {
};
ATTRIBUTE_GROUPS(mtd);
-static const struct device_type mtd_devtype = {
- .name = "mtd",
- .groups = mtd_groups,
- .release = mtd_release,
-};
-
static bool mtd_expert_analysis_mode;
#ifdef CONFIG_DEBUG_FS
@@ -434,7 +471,7 @@ DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_free);
static void mtd_debugfs_populate(struct mtd_info *mtd)
{
- struct device *dev = &mtd->dev;
+ struct device *dev = mtd->dev;
struct mtd_oob_region region;
if (IS_ERR_OR_NULL(dfs_dir_mtd))
@@ -610,9 +647,9 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
struct nvmem_config config = {};
config.id = NVMEM_DEVID_NONE;
- config.dev = &mtd->dev;
- config.name = dev_name(&mtd->dev);
- config.owner = THIS_MODULE;
+ config.dev = mtd->dev;
+ config.name = dev_name(mtd->dev);
+ 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;
@@ -629,7 +666,7 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
if (PTR_ERR(mtd->nvmem) == -EOPNOTSUPP)
mtd->nvmem = NULL;
else
- return dev_err_probe(&mtd->dev, PTR_ERR(mtd->nvmem),
+ return dev_err_probe(mtd->dev, PTR_ERR(mtd->nvmem),
"Failed to register NVMEM device\n");
}
@@ -703,6 +740,7 @@ int add_mtd_device(struct mtd_info *mtd)
struct device_node *np = mtd_get_of_node(mtd);
struct mtd_info *master = mtd_get_master(mtd);
struct mtd_notifier *not;
+ struct device *dev;
int i, error, ofidx;
/*
@@ -710,7 +748,7 @@ int add_mtd_device(struct mtd_info *mtd)
* mtd_device_parse_register() multiple times on the same master MTD,
* especially with CONFIG_MTD_PARTITIONED_MASTER=y.
*/
- if (WARN_ONCE(mtd->dev.type, "MTD already registered\n"))
+ if (WARN_ONCE(mtd->dev, "MTD already registered\n"))
return -EEXIST;
BUG_ON(mtd->writesize == 0);
@@ -756,6 +794,13 @@ int add_mtd_device(struct mtd_info *mtd)
mtd->index = i;
kref_init(&mtd->refcnt);
+ kref_init(&mtd->data_refcnt);
+ if (master->master.release) {
+ mtd_get_lifetime(master);
+ mtd->ref_master = master;
+ if (mtd->parent && mtd->parent != master)
+ mtd_get_data(mtd->parent);
+ }
/* default value if not set by driver */
if (mtd->bitflip_threshold == 0)
@@ -793,18 +838,21 @@ int add_mtd_device(struct mtd_info *mtd)
error = 0;
}
- /* Caller should have set dev.parent to match the
- * physical device, if appropriate.
- */
- mtd->dev.type = &mtd_devtype;
- mtd->dev.class = &mtd_class;
- mtd->dev.devt = MTD_DEVT(i);
- error = dev_set_name(&mtd->dev, "mtd%d", i);
- if (error)
- goto fail_devname;
- dev_set_drvdata(&mtd->dev, mtd);
mtd_set_of_node(mtd, mtd_find_of_node(mtd));
- error = device_register(&mtd->dev);
+ dev = mtd_class_device_alloc(mtd_get_of_node(mtd));
+ if (!dev) {
+ error = -ENOMEM;
+ goto fail_alloc;
+ }
+ mtd->dev = dev;
+ dev->parent = mtd->parent_dev;
+ dev->groups = mtd_groups;
+ dev->devt = MTD_DEVT(i);
+ dev_set_drvdata(dev, mtd);
+ error = dev_set_name(dev, "mtd%d", i);
+ if (error)
+ goto fail_added;
+ error = device_add(dev);
if (error)
goto fail_added;
@@ -815,10 +863,12 @@ int add_mtd_device(struct mtd_info *mtd)
mtd_debugfs_populate(mtd);
- device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
+ device_create(&mtd_class, mtd->parent_dev, MTD_DEVT(i) + 1, NULL,
"mtd%dro", i);
pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
+ __module_get(THIS_MODULE);
+ 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,27 +886,24 @@ 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);
+ device_del(dev);
fail_added:
- /*
- * Clear type and set nop 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;
- put_device(&mtd->dev);
+ mtd_device_detach(dev);
+ put_device(dev);
+ mtd->dev = NULL;
+fail_alloc:
of_node_put(mtd_get_of_node(mtd));
-fail_devname:
+ mtd_set_of_node(mtd, np);
idr_remove(&mtd_idr, i);
+ if (mtd->ref_master) {
+ mtd->ref_master = NULL;
+ if (mtd->parent && mtd->parent != master)
+ mtd_put_data(mtd->parent);
+ mtd_put_lifetime(master);
+ }
fail_locked:
mutex_unlock(&mtd_table_mutex);
return error;
@@ -879,7 +926,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 +936,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;
@@ -901,13 +949,13 @@ 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)
- mtd->owner = mtd->dev.parent->driver->owner;
+ if (mtd->parent_dev) {
+ if (!mtd->master.release && !mtd->owner && mtd->parent_dev->driver)
+ mtd->owner = mtd->parent_dev->driver->owner;
if (!mtd->name)
- mtd->name = dev_name(mtd->dev.parent);
+ mtd->name = dev_name(mtd->parent_dev);
} else {
pr_debug("mtd device won't show a device symlink in sysfs\n");
}
@@ -960,13 +1008,13 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
struct device_node *np;
/* DT binding is optional */
- np = of_get_compatible_child(mtd->dev.of_node, compatible);
+ np = of_get_compatible_child(mtd_get_of_node(mtd), compatible);
/* OTP nvmem will be registered on the physical device */
- config.dev = mtd->dev.parent;
+ config.dev = mtd->parent_dev;
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;
@@ -1016,7 +1064,7 @@ static int mtd_nvmem_fact_otp_reg_read(void *priv, unsigned int offset,
static int mtd_otp_nvmem_add(struct mtd_info *mtd)
{
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
struct nvmem_device *nvmem;
ssize_t size;
int err;
@@ -1150,7 +1198,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
ret = 0;
else if (nr_parts)
ret = add_mtd_partitions(mtd, parts, nr_parts);
- else if (!device_is_registered(&mtd->dev))
+ else if (!mtd->dev || !device_is_registered(mtd->dev))
ret = add_mtd_device(mtd);
else
ret = 0;
@@ -1184,7 +1232,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
nvmem_unregister(mtd->otp_factory_nvmem);
}
- if (ret && device_is_registered(&mtd->dev)) {
+ if (ret && mtd->dev && device_is_registered(mtd->dev)) {
err = del_mtd_device(mtd);
if (err)
pr_err("Error when deleting MTD device (%d)\n", err);
@@ -1225,7 +1273,7 @@ int mtd_device_unregister(struct mtd_info *master)
if (err)
return err;
- if (!device_is_registered(&master->dev))
+ if (!master->dev || !device_is_registered(master->dev))
return 0;
return del_mtd_device(master);
@@ -1328,7 +1376,6 @@ struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num)
}
EXPORT_SYMBOL_GPL(get_mtd_device);
-
/**
* get_mtd_device_by_dev - acquire the MTD associated with a class device
* @dev: referenced MTD class device
@@ -1342,7 +1389,7 @@ struct mtd_info *get_mtd_device_by_dev(struct device *dev)
guard(mutex)(&mtd_table_mutex);
mtd_for_each_device(mtd) {
- if (&mtd->dev != dev)
+ if (mtd->dev != dev)
continue;
err = __get_mtd_device(mtd);
return err ? ERR_PTR(err) : mtd;
@@ -1355,8 +1402,12 @@ EXPORT_SYMBOL_GPL(get_mtd_device_by_dev);
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)
@@ -1369,13 +1420,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;
@@ -1460,8 +1512,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 */
@@ -1472,12 +1526,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);
diff --git a/drivers/mtd/mtdcore.h b/drivers/mtd/mtdcore.h
index b014861a06a6..cf8f08d372a6 100644
--- a/drivers/mtd/mtdcore.h
+++ b/drivers/mtd/mtdcore.h
@@ -10,6 +10,11 @@ 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);
+void mtd_get_data(struct mtd_info *mtd);
+void mtd_put_data(struct mtd_info *mtd);
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/mtddevice.c b/drivers/mtd/mtddevice.c
new file mode 100644
index 000000000000..83ad70c0d202
--- /dev/null
+++ b/drivers/mtd/mtddevice.c
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/mtd/mtd.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+
+#include "mtddevice.h"
+
+static int mtd_cls_suspend(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+
+ return mtd ? mtd_suspend(mtd) : 0;
+}
+
+static int mtd_cls_resume(struct device *dev)
+{
+ struct mtd_info *mtd = dev_get_drvdata(dev);
+
+ if (mtd)
+ mtd_resume(mtd);
+ return 0;
+}
+static DEFINE_SIMPLE_DEV_PM_OPS(mtd_cls_pm_ops, mtd_cls_suspend, mtd_cls_resume);
+
+struct class mtd_class = {
+ .name = "mtd",
+ .pm = pm_sleep_ptr(&mtd_cls_pm_ops),
+};
+EXPORT_SYMBOL_GPL(mtd_class);
+
+static const struct device_type mtd_devtype = {
+ .name = "mtd",
+};
+
+/* Device references may outlive both the controller and the MTD module. */
+static void mtd_class_device_release(struct device *dev)
+{
+ of_node_put(dev->of_node);
+ kfree(dev);
+}
+
+struct device *mtd_class_device_alloc(struct device_node *np)
+{
+ struct device *dev = kzalloc_obj(*dev);
+
+ if (!dev)
+ return NULL;
+
+ device_initialize(dev);
+ dev->release = mtd_class_device_release;
+ dev->type = &mtd_devtype;
+ dev->class = &mtd_class;
+ dev->of_node = of_node_get(np);
+ return dev;
+}
+EXPORT_SYMBOL_GPL(mtd_class_device_alloc);
diff --git a/drivers/mtd/mtddevice.h b/drivers/mtd/mtddevice.h
new file mode 100644
index 000000000000..429557dbaab1
--- /dev/null
+++ b/drivers/mtd/mtddevice.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MTD_DEVICE_H
+#define __MTD_DEVICE_H
+
+struct device;
+struct device_node;
+struct class;
+
+extern struct class mtd_class;
+struct device *mtd_class_device_alloc(struct device_node *np);
+
+#endif
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index c316cfe92c7e..eb7704a5d714 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -87,9 +87,9 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
* parent conditional on that option. Note, this is a way to
* distinguish between the parent and its partitions in sysfs.
*/
- child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ?
- &parent->dev : parent->dev.parent;
- child->dev.of_node = part->of_node;
+ child->parent_dev = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ?
+ parent->dev : parent->parent_dev;
+ child->of_node = part->of_node;
child->parent = parent;
child->part.offset = part->offset;
INIT_LIST_HEAD(&child->partitions);
@@ -239,7 +239,7 @@ static const struct attribute *mtd_partition_attrs[] = {
static int mtd_add_partition_attrs(struct mtd_info *new)
{
- int ret = sysfs_create_files(&new->dev.kobj, mtd_partition_attrs);
+ int ret = sysfs_create_files(&new->dev->kobj, mtd_partition_attrs);
if (ret)
printk(KERN_WARNING
"mtd: failed to create partition attrs, err=%d\n", ret);
@@ -329,7 +329,7 @@ static int __mtd_del_partition(struct mtd_info *mtd)
return err;
}
- sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
+ sysfs_remove_files(&mtd->dev->kobj, mtd_partition_attrs);
list_del_init(&mtd->part.node);
err = del_mtd_device(mtd);
@@ -613,10 +613,10 @@ static int mtd_part_of_parse(struct mtd_info *master,
const char *fixed = "fixed-partitions";
int ret, err = 0;
- dev = &master->dev;
+ dev = master->dev;
/* Use parent device (controller) if the top level MTD is not registered */
if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) && !mtd_is_partition(master))
- dev = master->dev.parent;
+ dev = master->parent_dev;
np = mtd_get_of_node(master);
if (mtd_is_partition(master))
diff --git a/drivers/mtd/mtdpstore.c b/drivers/mtd/mtdpstore.c
index 9cf3872e37ae..64776d434bf6 100644
--- a/drivers/mtd/mtdpstore.c
+++ b/drivers/mtd/mtdpstore.c
@@ -37,7 +37,7 @@ static int mtdpstore_block_isbad(struct mtdpstore_context *cxt, loff_t off)
return true;
ret = mtd_block_isbad(mtd, off);
if (ret < 0) {
- dev_err(&mtd->dev, "mtd_block_isbad failed, aborting\n");
+ dev_err(mtd->dev, "mtd_block_isbad failed, aborting\n");
return ret;
} else if (ret > 0) {
set_bit(blknum, cxt->badmap);
@@ -63,7 +63,7 @@ static inline void mtdpstore_mark_used(struct mtdpstore_context *cxt,
struct mtd_info *mtd = cxt->mtd;
u64 zonenum = div_u64(off, cxt->info.kmsg_size);
- dev_dbg(&mtd->dev, "mark zone %llu used\n", zonenum);
+ dev_dbg(mtd->dev, "mark zone %llu used\n", zonenum);
set_bit(zonenum, cxt->usedmap);
}
@@ -73,7 +73,7 @@ static inline void mtdpstore_mark_unused(struct mtdpstore_context *cxt,
struct mtd_info *mtd = cxt->mtd;
u64 zonenum = div_u64(off, cxt->info.kmsg_size);
- dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum);
+ dev_dbg(mtd->dev, "mark zone %llu unused\n", zonenum);
clear_bit(zonenum, cxt->usedmap);
}
@@ -87,7 +87,7 @@ static inline void mtdpstore_block_mark_unused(struct mtdpstore_context *cxt,
off = ALIGN_DOWN(off, mtd->erasesize);
zonenum = div_u64(off, cxt->info.kmsg_size);
while (zonecnt > 0) {
- dev_dbg(&mtd->dev, "mark zone %llu unused\n", zonenum);
+ dev_dbg(mtd->dev, "mark zone %llu unused\n", zonenum);
clear_bit(zonenum, cxt->usedmap);
zonenum++;
zonecnt--;
@@ -142,7 +142,7 @@ static void mtdpstore_mark_removed(struct mtdpstore_context *cxt, loff_t off)
struct mtd_info *mtd = cxt->mtd;
u64 zonenum = div_u64(off, cxt->info.kmsg_size);
- dev_dbg(&mtd->dev, "mark zone %llu removed\n", zonenum);
+ dev_dbg(mtd->dev, "mark zone %llu removed\n", zonenum);
set_bit(zonenum, cxt->rmmap);
}
@@ -187,14 +187,14 @@ static int mtdpstore_erase_do(struct mtdpstore_context *cxt, loff_t off)
int ret;
off = ALIGN_DOWN(off, cxt->mtd->erasesize);
- dev_dbg(&mtd->dev, "try to erase off 0x%llx\n", off);
+ dev_dbg(mtd->dev, "try to erase off 0x%llx\n", off);
erase.len = cxt->mtd->erasesize;
erase.addr = off;
ret = mtd_erase(cxt->mtd, &erase);
if (!ret)
mtdpstore_block_clear_removed(cxt, off);
else
- dev_err(&mtd->dev, "erase of region [0x%llx, 0x%llx] on \"%s\" failed\n",
+ dev_err(mtd->dev, "erase of region [0x%llx, 0x%llx] on \"%s\" failed\n",
(unsigned long long)erase.addr,
(unsigned long long)erase.len, cxt->info.device);
return ret;
@@ -266,8 +266,8 @@ static int mtdpstore_security(struct mtdpstore_context *cxt, loff_t off)
}
if (ret)
- dev_err(&mtd->dev, "all blocks bad!\n");
- dev_dbg(&mtd->dev, "end security\n");
+ dev_err(mtd->dev, "all blocks bad!\n");
+ dev_dbg(mtd->dev, "end security\n");
return ret;
}
@@ -285,10 +285,10 @@ static ssize_t mtdpstore_write(const char *buf, size_t size, loff_t off)
if (mtdpstore_is_used(cxt, off))
return -ENOMSG;
- dev_dbg(&mtd->dev, "try to write off 0x%llx size %zu\n", off, size);
+ dev_dbg(mtd->dev, "try to write off 0x%llx size %zu\n", off, size);
ret = mtd_write(cxt->mtd, off, size, &retlen, (u_char *)buf);
if (ret < 0 || retlen != size) {
- dev_err(&mtd->dev, "write failure at %lld (%zu of %zu written), err %d\n",
+ dev_err(mtd->dev, "write failure at %lld (%zu of %zu written), err %d\n",
off, retlen, size, ret);
return -EIO;
}
@@ -317,14 +317,14 @@ static ssize_t mtdpstore_read(char *buf, size_t size, loff_t off)
if (mtdpstore_block_isbad(cxt, off))
return -ENOMSG;
- dev_dbg(&mtd->dev, "try to read off 0x%llx size %zu\n", off, size);
+ dev_dbg(mtd->dev, "try to read off 0x%llx size %zu\n", off, size);
for (done = 0, retlen = 0; done < size; done += retlen) {
retlen = 0;
ret = mtd_read(cxt->mtd, off + done, size - done, &retlen,
(u_char *)buf + done);
if (mtdpstore_is_io_error(ret)) {
- dev_err(&mtd->dev, "read failure at %lld (%zu of %zu read), err %d\n",
+ dev_err(mtd->dev, "read failure at %lld (%zu of %zu read), err %d\n",
off + done, retlen, size - done, ret);
/* the zone may be broken, try next one */
return -ENOMSG;
@@ -337,7 +337,7 @@ static ssize_t mtdpstore_read(char *buf, size_t size, loff_t off)
* valid or not.
*/
if (mtd_is_eccerr(ret)) {
- dev_err(&mtd->dev, "ecc error at %lld (%zu of %zu read), err %d\n",
+ dev_err(mtd->dev, "ecc error at %lld (%zu of %zu read), err %d\n",
off + done, retlen, size - done, ret);
/* driver may not set retlen when ecc error */
retlen = retlen == 0 ? size - done : retlen;
@@ -369,7 +369,7 @@ static ssize_t mtdpstore_panic_write(const char *buf, size_t size, loff_t off)
ret = mtd_panic_write(cxt->mtd, off, size, &retlen, (u_char *)buf);
if (ret < 0 || size != retlen) {
- dev_err(&mtd->dev, "panic write failure at %lld (%zu of %zu read), err %d\n",
+ dev_err(mtd->dev, "panic write failure at %lld (%zu of %zu read), err %d\n",
off, retlen, size, ret);
return -EIO;
}
@@ -391,10 +391,10 @@ static void mtdpstore_notify_add(struct mtd_info *mtd)
if (mtd->index != cxt->index || cxt->index < 0)
return;
- dev_dbg(&mtd->dev, "found matching MTD device %s\n", mtd->name);
+ dev_dbg(mtd->dev, "found matching MTD device %s\n", mtd->name);
if (mtd->size < info->kmsg_size * 2) {
- dev_err(&mtd->dev, "MTD partition %d not big enough\n",
+ dev_err(mtd->dev, "MTD partition %d not big enough\n",
mtd->index);
return;
}
@@ -405,23 +405,23 @@ static void mtdpstore_notify_add(struct mtd_info *mtd)
* is designed on it.
*/
if (mtd->erasesize < info->kmsg_size) {
- dev_err(&mtd->dev, "eraseblock size of MTD partition %d too small\n",
+ dev_err(mtd->dev, "eraseblock size of MTD partition %d too small\n",
mtd->index);
return;
}
if (unlikely(info->kmsg_size % mtd->writesize)) {
- dev_err(&mtd->dev, "record size %lu KB must align to write size %d KB\n",
+ dev_err(mtd->dev, "record size %lu KB must align to write size %d KB\n",
info->kmsg_size / 1024,
mtd->writesize / 1024);
return;
}
longcnt = BITS_TO_LONGS(div_u64(mtd->size, info->kmsg_size));
- cxt->rmmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
- cxt->usedmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
+ cxt->rmmap = devm_kcalloc(mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
+ cxt->usedmap = devm_kcalloc(mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
longcnt = BITS_TO_LONGS(div_u64(mtd->size, mtd->erasesize));
- cxt->badmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
+ cxt->badmap = devm_kcalloc(mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
if (!cxt->rmmap || !cxt->usedmap || !cxt->badmap)
return;
@@ -436,12 +436,12 @@ static void mtdpstore_notify_add(struct mtd_info *mtd)
ret = register_pstore_device(&cxt->dev);
if (ret) {
- dev_err(&mtd->dev, "mtd%d register to psblk failed\n",
+ dev_err(mtd->dev, "mtd%d register to psblk failed\n",
mtd->index);
return;
}
cxt->mtd = mtd;
- dev_info(&mtd->dev, "Attached to MTD device %d\n", mtd->index);
+ dev_info(mtd->dev, "Attached to MTD device %d\n", mtd->index);
}
static int mtdpstore_flush_removed_do(struct mtdpstore_context *cxt,
@@ -477,7 +477,7 @@ static int mtdpstore_flush_removed_do(struct mtdpstore_context *cxt,
if (mtdpstore_is_used(cxt, off)) {
ret = mtd_write(mtd, off, zonesize, &retlen, buf);
if (ret)
- dev_err(&mtd->dev, "write failure at %lld (%zu of %u written), err %d\n",
+ dev_err(mtd->dev, "write failure at %lld (%zu of %u written), err %d\n",
off, retlen, zonesize, ret);
}
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 6ccdff3fc913..cd507a68aec5 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -664,7 +664,6 @@ static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *d
struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
{
struct nand_ecc_engine *engine = NULL;
- struct device *dev = &nand->mtd.dev;
struct platform_device *pdev;
struct device_node *np;
@@ -672,7 +671,7 @@ struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand)
return NULL;
/* Check for an explicit nand-ecc-engine property */
- np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0);
+ np = of_parse_phandle(mtd_get_of_node(&nand->mtd), "nand-ecc-engine", 0);
if (np) {
pdev = of_find_device_by_node(np);
if (!pdev)
diff --git a/drivers/mtd/nand/onenand/generic.c b/drivers/mtd/nand/onenand/generic.c
index 3191904ced14..8f82b86b8b83 100644
--- a/drivers/mtd/nand/onenand/generic.c
+++ b/drivers/mtd/nand/onenand/generic.c
@@ -60,7 +60,7 @@ static int generic_onenand_probe(struct platform_device *pdev)
info->onenand.irq = err;
- info->mtd.dev.parent = &pdev->dev;
+ info->mtd.parent_dev = &pdev->dev;
info->mtd.priv = &info->onenand;
if (onenand_scan(&info->mtd, 1)) {
diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c
index 644498187d8f..5890e13a29d0 100644
--- a/drivers/mtd/nand/onenand/onenand_omap2.c
+++ b/drivers/mtd/nand/onenand/onenand_omap2.c
@@ -513,7 +513,7 @@ static int omap2_onenand_probe(struct platform_device *pdev)
c->pdev = pdev;
c->mtd.priv = &c->onenand;
- c->mtd.dev.parent = dev;
+ c->mtd.parent_dev = dev;
mtd_set_of_node(&c->mtd, dev->of_node);
dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n",
diff --git a/drivers/mtd/nand/onenand/onenand_samsung.c b/drivers/mtd/nand/onenand/onenand_samsung.c
index b7b7758ce4d8..a9f465f281b3 100644
--- a/drivers/mtd/nand/onenand/onenand_samsung.c
+++ b/drivers/mtd/nand/onenand/onenand_samsung.c
@@ -859,7 +859,7 @@ static int s3c_onenand_probe(struct platform_device *pdev)
this = (struct onenand_chip *) &mtd[1];
mtd->priv = this;
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
onenand->pdev = pdev;
onenand->type = platform_get_device_id(pdev)->driver_data;
diff --git a/drivers/mtd/nand/raw/ams-delta.c b/drivers/mtd/nand/raw/ams-delta.c
index fb2b7db70297..1afeb38145cd 100644
--- a/drivers/mtd/nand/raw/ams-delta.c
+++ b/drivers/mtd/nand/raw/ams-delta.c
@@ -196,7 +196,7 @@ static int gpio_nand_setup_interface(struct nand_chip *this, int csline,
{
struct gpio_nand *priv = nand_get_controller_data(this);
const struct nand_sdr_timings *sdr = nand_get_sdr_timings(cf);
- struct device *dev = &nand_to_mtd(this)->dev;
+ struct device *dev = nand_to_mtd(this)->parent_dev;
if (IS_ERR(sdr))
return PTR_ERR(sdr);
@@ -258,7 +258,7 @@ static int gpio_nand_probe(struct platform_device *pdev)
this = &priv->nand_chip;
mtd = nand_to_mtd(this);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_controller_data(this, priv);
nand_set_flash_node(this, pdev->dev.of_node);
diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c
index 865754737f5f..839aef6c460b 100644
--- a/drivers/mtd/nand/raw/arasan-nand-controller.c
+++ b/drivers/mtd/nand/raw/arasan-nand-controller.c
@@ -1315,7 +1315,7 @@ static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np)
chip = &anand->chip;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = nfc->dev;
+ mtd->parent_dev = nfc->dev;
chip->controller = &nfc->controller;
chip->options = NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE |
NAND_USES_DMA;
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index 9a724de67624..0c79d30b9e30 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -1575,7 +1575,7 @@ static void atmel_nand_init(struct atmel_nand_controller *nc,
struct nand_chip *chip = &nand->base;
struct mtd_info *mtd = nand_to_mtd(chip);
- mtd->dev.parent = nc->dev;
+ mtd->parent_dev = nc->dev;
nand->base.controller = &nc->base;
if (!nc->mck || !nc->caps->ops->setup_interface)
diff --git a/drivers/mtd/nand/raw/au1550nd.c b/drivers/mtd/nand/raw/au1550nd.c
index 6796afe9cd29..01c2c8b191eb 100644
--- a/drivers/mtd/nand/raw/au1550nd.c
+++ b/drivers/mtd/nand/raw/au1550nd.c
@@ -291,7 +291,7 @@ static int au1550nd_probe(struct platform_device *pdev)
this = &ctx->chip;
mtd = nand_to_mtd(this);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
/* figure out which CS# r->start belongs to */
cs = find_nand_cs(r->start);
diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/main.c b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
index 4d4e185c22e5..4cccf00f67f7 100644
--- a/drivers/mtd/nand/raw/bcm47xxnflash/main.c
+++ b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
@@ -32,7 +32,7 @@ static int bcm47xxnflash_probe(struct platform_device *pdev)
nand_set_controller_data(&b47n->nand_chip, b47n);
mtd = nand_to_mtd(&b47n->nand_chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
b47n->cc = container_of(nflash, struct bcma_drv_cc, nflash);
if (b47n->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) {
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index 4a1f5e736a1c..0378014da6d2 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -3097,7 +3097,7 @@ static int brcmnand_init_cs(struct brcmnand_host *host,
return -ENOMEM;
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
chip->ecc.read_page = brcmnand_read_page;
diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c
index d53b35a8b3cb..05d0659e6527 100644
--- a/drivers/mtd/nand/raw/cadence-nand-controller.c
+++ b/drivers/mtd/nand/raw/cadence-nand-controller.c
@@ -3048,7 +3048,7 @@ static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl,
nand_set_flash_node(chip, np);
mtd = nand_to_mtd(chip);
- mtd->dev.parent = cdns_ctrl->dev;
+ mtd->parent_dev = cdns_ctrl->dev;
/*
* Default to HW ECC engine mode. If the nand-ecc-mode property is given
diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c
index f3117b031cd2..e936c9807bf8 100644
--- a/drivers/mtd/nand/raw/cafe_nand.c
+++ b/drivers/mtd/nand/raw/cafe_nand.c
@@ -685,7 +685,7 @@ static int cafe_nand_probe(struct pci_dev *pdev,
}
mtd = nand_to_mtd(&cafe->nand);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_controller_data(&cafe->nand, cafe);
cafe->pdev = pdev;
diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c
index 3986553881d0..17c6fa73f4c3 100644
--- a/drivers/mtd/nand/raw/davinci_nand.c
+++ b/drivers/mtd/nand/raw/davinci_nand.c
@@ -958,7 +958,7 @@ static int nand_davinci_probe(struct platform_device *pdev)
info->aemif = dev_get_drvdata(pdev->dev.parent);
mtd = nand_to_mtd(&info->chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_flash_node(&info->chip, pdev->dev.of_node);
/* options such as NAND_BBT_USE_FLASH */
diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c
index fa2439cb4daa..9c07e4e25a27 100644
--- a/drivers/mtd/nand/raw/denali.c
+++ b/drivers/mtd/nand/raw/denali.c
@@ -1216,7 +1216,7 @@ int denali_chip_init(struct denali_controller *denali,
}
}
- mtd->dev.parent = denali->dev;
+ mtd->parent_dev = denali->dev;
/*
* Fallback to the default name if DT did not give "label" property.
diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c
index c655d27bc4cc..6216ffea4324 100644
--- a/drivers/mtd/nand/raw/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c
@@ -687,7 +687,7 @@ static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv)
dev_dbg(priv->dev, "eLBC Set Information for bank %d\n", priv->bank);
/* Fill in fsl_elbc_mtd structure */
- mtd->dev.parent = priv->dev;
+ mtd->parent_dev = priv->dev;
nand_set_flash_node(chip, priv->dev->of_node);
/* set timeout to maximum */
diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c
index cc75b6a67e07..20ae012adbaa 100644
--- a/drivers/mtd/nand/raw/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c
@@ -868,7 +868,7 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
int ret;
/* Fill in fsl_ifc_mtd structure */
- mtd->dev.parent = priv->dev;
+ mtd->parent_dev = priv->dev;
struct device_node *np __free(device_node) =
of_get_next_child_with_prefix(priv->dev->of_node, NULL, "nand");
diff --git a/drivers/mtd/nand/raw/fsl_upm.c b/drivers/mtd/nand/raw/fsl_upm.c
index f4dc990a8da1..28335dedb987 100644
--- a/drivers/mtd/nand/raw/fsl_upm.c
+++ b/drivers/mtd/nand/raw/fsl_upm.c
@@ -50,7 +50,7 @@ static int fun_chip_init(struct fsl_upm_nand *fun,
fun->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT;
fun->chip.ecc.algo = NAND_ECC_ALGO_HAMMING;
fun->chip.controller = &fun->base;
- mtd->dev.parent = fun->dev;
+ mtd->parent_dev = fun->dev;
flash_np = of_get_next_child(upm_np, NULL);
if (!flash_np)
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c
index 0b1817cdfc5a..3cd25395d791 100644
--- a/drivers/mtd/nand/raw/fsmc_nand.c
+++ b/drivers/mtd/nand/raw/fsmc_nand.c
@@ -1102,7 +1102,7 @@ static int __init fsmc_nand_probe(struct platform_device *pdev)
mtd = nand_to_mtd(&host->nand);
nand_set_flash_node(nand, pdev->dev.of_node);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand->badblockbits = 7;
diff --git a/drivers/mtd/nand/raw/gpio.c b/drivers/mtd/nand/raw/gpio.c
index 69e5e43532a4..d17fb2244923 100644
--- a/drivers/mtd/nand/raw/gpio.c
+++ b/drivers/mtd/nand/raw/gpio.c
@@ -353,7 +353,7 @@ static int gpio_nand_probe(struct platform_device *pdev)
chip->controller = &gpiomtd->base;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
platform_set_drvdata(pdev, gpiomtd);
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index 527165ccc839..4822485bd512 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -2714,7 +2714,7 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
/* init the MTD data structures */
mtd->name = "gpmi-nand";
- mtd->dev.parent = this->dev;
+ mtd->parent_dev = this->dev;
/* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */
nand_set_controller_data(chip, this);
diff --git a/drivers/mtd/nand/raw/hisi504_nand.c b/drivers/mtd/nand/raw/hisi504_nand.c
index d97270ec1185..ccc21d21c3e0 100644
--- a/drivers/mtd/nand/raw/hisi504_nand.c
+++ b/drivers/mtd/nand/raw/hisi504_nand.c
@@ -762,7 +762,7 @@ static int hisi_nfc_probe(struct platform_device *pdev)
return PTR_ERR(host->mmio);
mtd->name = "hisi_nand";
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_controller_data(chip, host);
nand_set_flash_node(chip, np);
diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
index f1e2c82936b3..bdda06ae0d72 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
@@ -394,7 +394,7 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
cs->bank);
if (!mtd->name)
return -ENOMEM;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
chip->options = NAND_NO_SUBPAGE_WRITE;
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c
index 97c82e9c1af2..d8071180cc0d 100644
--- a/drivers/mtd/nand/raw/intel-nand-controller.c
+++ b/drivers/mtd/nand/raw/intel-nand-controller.c
@@ -677,7 +677,7 @@ static int ebu_nand_probe(struct platform_device *pdev)
goto err_cleanup_dma;
}
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
ebu_host->dev = dev;
platform_set_drvdata(pdev, ebu_host);
diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c
index 8490412d5be1..741e19f351d1 100644
--- a/drivers/mtd/nand/raw/loongson-nand-controller.c
+++ b/drivers/mtd/nand/raw/loongson-nand-controller.c
@@ -862,7 +862,7 @@ static int loongson_nand_chip_init(struct loongson_nand_host *host)
chip->controller = &host->controller;
chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD;
chip->buf_align = 16;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd->owner = THIS_MODULE;
ret = nand_scan(chip, 1);
diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c
index 8436015edcb4..408b2aba2fdd 100644
--- a/drivers/mtd/nand/raw/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c
@@ -328,7 +328,7 @@ static int lpc32xx_waitfunc_nand(struct nand_chip *chip)
while (!(readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)) {
/* Seems to be delayed sometimes by controller */
- dev_dbg(&mtd->dev, "Warning: NAND not ready.\n");
+ dev_dbg(mtd->dev, "Warning: NAND not ready.\n");
cpu_relax();
}
@@ -348,7 +348,7 @@ static int lpc32xx_waitfunc_controller(struct nand_chip *chip)
while (!(readb(MLC_ISR(host->io_base)) &
MLCISR_CONTROLLER_READY)) {
- dev_dbg(&mtd->dev, "Warning: Controller not ready.\n");
+ dev_dbg(mtd->dev, "Warning: Controller not ready.\n");
cpu_relax();
}
@@ -402,13 +402,13 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
if (res != 1) {
- dev_err(mtd->dev.parent, "Failed to map sg list\n");
+ dev_err(mtd->parent_dev, "Failed to map sg list\n");
return -ENXIO;
}
desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir,
flags);
if (!desc) {
- dev_err(mtd->dev.parent, "Failed to prepare slave sg\n");
+ dev_err(mtd->parent_dev, "Failed to prepare slave sg\n");
res = -ENXIO;
goto out1;
}
@@ -472,7 +472,7 @@ static int lpc32xx_read_page(struct nand_chip *chip, uint8_t *buf,
mlc_isr = readl(MLC_ISR(host->io_base));
if (mlc_isr & MLCISR_DECODER_FAILURE) {
mtd->ecc_stats.failed++;
- dev_warn(&mtd->dev, "%s: DECODER_FAILURE\n", __func__);
+ dev_warn(mtd->dev, "%s: DECODER_FAILURE\n", __func__);
} else if (mlc_isr & MLCISR_ERRORS_DETECTED) {
mtd->ecc_stats.corrected += ((mlc_isr >> 4) & 0x3) + 1;
}
@@ -579,9 +579,9 @@ static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host)
{
struct mtd_info *mtd = nand_to_mtd(&host->nand_chip);
- host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx");
+ host->dma_chan = dma_request_chan(mtd->parent_dev, "rx-tx");
if (IS_ERR(host->dma_chan)) {
- dev_err(mtd->dev.parent, "Failed to request DMA channel\n");
+ dev_err(mtd->parent_dev, "Failed to request DMA channel\n");
return PTR_ERR(host->dma_chan);
}
@@ -600,7 +600,7 @@ static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host)
host->dma_slave_config.src_addr = MLC_BUFF(host->io_base_phy);
host->dma_slave_config.dst_addr = MLC_BUFF(host->io_base_phy);
if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) {
- dev_err(mtd->dev.parent, "Failed to setup DMA slave\n");
+ dev_err(mtd->parent_dev, "Failed to setup DMA slave\n");
goto out1;
}
@@ -724,7 +724,7 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
/* link the private data structures */
nand_set_controller_data(nand_chip, host);
nand_set_flash_node(nand_chip, pdev->dev.of_node);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
/* Get NAND clock */
host->clk = clk_get(&pdev->dev, NULL);
diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c
index b4d90afa9267..f75ae484ce86 100644
--- a/drivers/mtd/nand/raw/lpc32xx_slc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_slc.c
@@ -440,7 +440,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma,
/* DMA controller does flow control: */
host->dma_slave_config.device_fc = false;
if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) {
- dev_err(mtd->dev.parent, "Failed to setup DMA slave\n");
+ dev_err(mtd->parent_dev, "Failed to setup DMA slave\n");
return -ENXIO;
}
@@ -449,13 +449,13 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma,
res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
if (res != 1) {
- dev_err(mtd->dev.parent, "Failed to map sg list\n");
+ dev_err(mtd->parent_dev, "Failed to map sg list\n");
return -ENXIO;
}
desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir,
flags);
if (!desc) {
- dev_err(mtd->dev.parent, "Failed to prepare slave sg\n");
+ dev_err(mtd->parent_dev, "Failed to prepare slave sg\n");
goto out1;
}
@@ -559,13 +559,13 @@ static int lpc32xx_xfer(struct mtd_info *mtd, uint8_t *buf, int eccsubpages,
* safety reasons for now.
*/
if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) {
- dev_warn(mtd->dev.parent, "FIFO not empty!\n");
+ dev_warn(mtd->parent_dev, "FIFO not empty!\n");
timeout = jiffies + msecs_to_jiffies(LPC32XX_DMA_TIMEOUT);
while ((readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) &&
time_before(jiffies, timeout))
cpu_relax();
if (!time_before(jiffies, timeout)) {
- dev_err(mtd->dev.parent, "FIFO held data too long\n");
+ dev_err(mtd->parent_dev, "FIFO held data too long\n");
status = -EIO;
}
}
@@ -582,7 +582,7 @@ static int lpc32xx_xfer(struct mtd_info *mtd, uint8_t *buf, int eccsubpages,
if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO ||
readl(SLC_TC(host->io_base))) {
/* Something is left in the FIFO, something is wrong */
- dev_err(mtd->dev.parent, "DMA FIFO failure\n");
+ dev_err(mtd->parent_dev, "DMA FIFO failure\n");
status = -EIO;
}
@@ -726,9 +726,9 @@ static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host)
{
struct mtd_info *mtd = nand_to_mtd(&host->nand_chip);
- host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx");
+ host->dma_chan = dma_request_chan(mtd->parent_dev, "rx-tx");
if (IS_ERR(host->dma_chan)) {
- dev_err(mtd->dev.parent, "Failed to request DMA channel\n");
+ dev_err(mtd->parent_dev, "Failed to request DMA channel\n");
return PTR_ERR(host->dma_chan);
}
@@ -863,7 +863,7 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
nand_set_controller_data(chip, host);
nand_set_flash_node(chip, pdev->dev.of_node);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
/* Get NAND clock */
host->clk = devm_clk_get_enabled(&pdev->dev, NULL);
diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c
index 38b7eb5b992c..56350209f7e0 100644
--- a/drivers/mtd/nand/raw/marvell_nand.c
+++ b/drivers/mtd/nand/raw/marvell_nand.c
@@ -2723,7 +2723,7 @@ static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc,
chip->options |= NAND_KEEP_TIMINGS;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
/*
* Save a reference value for timing registers before
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
index b8834aa96e81..2a554f62a25e 100644
--- a/drivers/mtd/nand/raw/meson_nand.c
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -1428,7 +1428,7 @@ meson_nfc_nand_chip_init(struct device *dev,
nand->options |= NAND_USES_DMA;
mtd = nand_to_mtd(nand);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
ret = of_property_read_u32(np, "nand-rb", &nand_rb_val);
if (ret == -EINVAL)
diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c
index e6594548b7e0..0aeeb6327472 100644
--- a/drivers/mtd/nand/raw/mpc5121_nfc.c
+++ b/drivers/mtd/nand/raw/mpc5121_nfc.c
@@ -655,7 +655,7 @@ static int mpc5121_nfc_probe(struct platform_device *op)
prv->controller.ops = &mpc5121_nfc_ops;
chip->controller = &prv->controller;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
nand_set_controller_data(chip, prv);
nand_set_flash_node(chip, dn);
prv->dev = dev;
diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c
index 21c7e1102746..a36a82d10aa7 100644
--- a/drivers/mtd/nand/raw/mtk_nand.c
+++ b/drivers/mtd/nand/raw/mtk_nand.c
@@ -1296,7 +1296,7 @@ static int mtk_nfc_ecc_init(struct device *dev, struct mtd_info *mtd)
static int mtk_nfc_attach_chip(struct nand_chip *chip)
{
struct mtd_info *mtd = nand_to_mtd(chip);
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
struct mtk_nfc *nfc = nand_get_controller_data(chip);
struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip);
int len;
@@ -1407,7 +1407,7 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
mtd = nand_to_mtd(nand);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd->name = MTK_NAME;
mtd_set_ooblayout(mtd, &mtk_nfc_ooblayout_ops);
diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c
index 4d8b92e7e672..f976cf3f7210 100644
--- a/drivers/mtd/nand/raw/mxc_nand.c
+++ b/drivers/mtd/nand/raw/mxc_nand.c
@@ -1398,7 +1398,7 @@ static int mxcnd_attach_chip(struct nand_chip *chip)
{
struct mtd_info *mtd = nand_to_mtd(chip);
struct mxc_nand_host *host = nand_get_controller_data(chip);
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
chip->ecc.bytes = host->devtype_data->eccbytes;
host->eccsize = host->devtype_data->eccsize;
@@ -1708,7 +1708,7 @@ static int mxcnd_probe(struct platform_device *pdev)
/* structures must be linked */
this = &host->nand;
mtd = nand_to_mtd(this);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
mtd->name = DRIVER_NAME;
/* 50 us command delay time */
diff --git a/drivers/mtd/nand/raw/mxic_nand.c b/drivers/mtd/nand/raw/mxic_nand.c
index 92de26697359..ce9fe27fb4ac 100644
--- a/drivers/mtd/nand/raw/mxic_nand.c
+++ b/drivers/mtd/nand/raw/mxic_nand.c
@@ -515,7 +515,7 @@ static int mxic_nfc_probe(struct platform_device *pdev)
nand_chip = &nfc->chip;
mtd = nand_to_mtd(nand_chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
for_each_child_of_node(np, nand_np)
nand_set_flash_node(nand_chip, nand_np);
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index f125c529c62d..f6a054919c06 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -5576,8 +5576,8 @@ static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips,
if (ret)
return ret;
- if (!mtd->name && mtd->dev.parent)
- mtd->name = dev_name(mtd->dev.parent);
+ if (!mtd->name && mtd->parent_dev)
+ mtd->name = dev_name(mtd->parent_dev);
/* Set the default functions */
nand_set_defaults(chip);
diff --git a/drivers/mtd/nand/raw/nand_macronix.c b/drivers/mtd/nand/raw/nand_macronix.c
index 4fe7036ae992..7169c014d197 100644
--- a/drivers/mtd/nand/raw/nand_macronix.c
+++ b/drivers/mtd/nand/raw/nand_macronix.c
@@ -419,11 +419,11 @@ static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd,
out_otp:
if (ret)
- dev_err(&mtd->dev, "failed to perform OTP IO: %i\n", ret);
+ dev_err(mtd->dev, "failed to perform OTP IO: %i\n", ret);
ret = macronix_30lfxg18ac_otp_disable(nand);
if (ret)
- dev_err(&mtd->dev, "failed to leave OTP mode after %s\n",
+ dev_err(mtd->dev, "failed to leave OTP mode after %s\n",
write ? "write" : "read");
nand_deselect_target(nand);
diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c
index b5e304c35738..4bacc1c89c6b 100644
--- a/drivers/mtd/nand/raw/nand_onfi.c
+++ b/drivers/mtd/nand/raw/nand_onfi.c
@@ -87,7 +87,7 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
s = ep->sections + i;
section_len = s->length * 16;
if (section_len > remaining) {
- dev_dbg(&mtd->dev,
+ dev_dbg(mtd->dev,
"ONFI extended parameter section %d exceeds page\n",
i);
goto ext_out;
@@ -95,7 +95,7 @@ static int nand_flash_detect_ext_param_page(struct nand_chip *chip,
if (s->type == ONFI_SECTION_TYPE_2) {
if (section_len < sizeof(*ecc)) {
- dev_dbg(&mtd->dev,
+ dev_dbg(mtd->dev,
"ONFI extended parameter ECC section %d is too short\n",
i);
goto ext_out;
diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c
index d7b119028a71..ce10fc6a41aa 100644
--- a/drivers/mtd/nand/raw/ndfc.c
+++ b/drivers/mtd/nand/raw/ndfc.c
@@ -155,7 +155,7 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc,
chip->ecc.strength = 1;
nand_set_controller_data(chip, ndfc);
- mtd->dev.parent = &ndfc->ofdev->dev;
+ mtd->parent_dev = &ndfc->ofdev->dev;
flash_np = of_get_next_child(node, NULL);
if (!flash_np)
diff --git a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
index f659d3a6b9ef..23db2f4fefbb 100644
--- a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
+++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
@@ -235,7 +235,7 @@ static int ma35_nand_hwecc_init(struct nand_chip *chip, struct ma35_nand_info *n
{
struct ma35_nand_chip *nvtnand = to_ma35_nand(chip);
struct mtd_info *mtd = nand_to_mtd(chip);
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
u32 reg;
nand->buffer = devm_kzalloc(dev, mtd->writesize, GFP_KERNEL);
@@ -736,7 +736,7 @@ static int ma35_nand_attach_chip(struct nand_chip *chip)
{
struct ma35_nand_info *nand = nand_get_controller_data(chip);
struct mtd_info *mtd = nand_to_mtd(chip);
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
u32 reg;
if (chip->options & NAND_BUSWIDTH_16) {
@@ -900,7 +900,7 @@ static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
mtd = nand_to_mtd(chip);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd_set_ooblayout(mtd, &ma35_ooblayout_ops);
ret = nand_scan(chip, nsels);
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index 4909e62dc11e..aee7df4d768f 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -2237,7 +2237,7 @@ static int omap_nand_probe(struct platform_device *pdev)
nand_chip = &info->nand;
mtd = nand_to_mtd(nand_chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_flash_node(nand_chip, dev->of_node);
if (!mtd->name) {
diff --git a/drivers/mtd/nand/raw/orion_nand.c b/drivers/mtd/nand/raw/orion_nand.c
index 47e80d5e58c5..6cec25425710 100644
--- a/drivers/mtd/nand/raw/orion_nand.c
+++ b/drivers/mtd/nand/raw/orion_nand.c
@@ -148,7 +148,7 @@ static int __init orion_nand_probe(struct platform_device *pdev)
board = dev_get_platdata(&pdev->dev);
}
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
nand_set_controller_data(nc, board);
nand_set_flash_node(nc, pdev->dev.of_node);
diff --git a/drivers/mtd/nand/raw/pasemi_nand.c b/drivers/mtd/nand/raw/pasemi_nand.c
index 09409b703d93..b497e84636c1 100644
--- a/drivers/mtd/nand/raw/pasemi_nand.c
+++ b/drivers/mtd/nand/raw/pasemi_nand.c
@@ -129,7 +129,7 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
pasemi_nand_mtd = nand_to_mtd(chip);
/* Link the private data with the MTD structure */
- pasemi_nand_mtd->dev.parent = dev;
+ pasemi_nand_mtd->parent_dev = dev;
chip->legacy.IO_ADDR_R = of_iomap(np, 0);
chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R;
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 26b61d856143..537387014e2a 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -1082,7 +1082,7 @@ static int pl35x_nand_chip_init(struct pl35x_nandc *nfc,
chip->bbt_options = NAND_BBT_USE_FLASH;
chip->controller = &nfc->controller;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = nfc->dev;
+ mtd->parent_dev = nfc->dev;
nand_set_flash_node(chip, np);
if (!mtd->name) {
mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL,
diff --git a/drivers/mtd/nand/raw/plat_nand.c b/drivers/mtd/nand/raw/plat_nand.c
index fe31551bcf5f..a2775886e7c4 100644
--- a/drivers/mtd/nand/raw/plat_nand.c
+++ b/drivers/mtd/nand/raw/plat_nand.c
@@ -88,7 +88,7 @@ static int plat_nand_probe(struct platform_device *pdev)
nand_set_flash_node(&data->chip, pdev->dev.of_node);
mtd = nand_to_mtd(&data->chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
data->chip.legacy.IO_ADDR_R = data->io_base;
data->chip.legacy.IO_ADDR_W = data->io_base;
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index 978fa84f051d..c2ccb999ad8d 100644
--- a/drivers/mtd/nand/raw/qcom_nandc.c
+++ b/drivers/mtd/nand/raw/qcom_nandc.c
@@ -2031,7 +2031,7 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
return -ENOMEM;
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
/*
* the bad block marker is readable only when we read the last codeword
diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c
index bf1c1be7f2aa..566662d45c2c 100644
--- a/drivers/mtd/nand/raw/r852.c
+++ b/drivers/mtd/nand/raw/r852.c
@@ -589,7 +589,7 @@ static void r852_update_card_detect(struct r852_device *dev)
static ssize_t media_type_show(struct device *sys_dev,
struct device_attribute *attr, char *buf)
{
- struct mtd_info *mtd = container_of(sys_dev, struct mtd_info, dev);
+ struct mtd_info *mtd = dev_get_drvdata(sys_dev);
struct r852_device *dev = r852_get_dev(mtd);
char *data = dev->sm ? "smartmedia" : "xd";
@@ -635,7 +635,7 @@ static int r852_register_nand_device(struct r852_device *dev)
WARN_ON(dev->card_registered);
- mtd->dev.parent = &dev->pci_dev->dev;
+ mtd->parent_dev = &dev->pci_dev->dev;
if (dev->readonly)
dev->chip->options |= NAND_ROM;
@@ -645,7 +645,7 @@ static int r852_register_nand_device(struct r852_device *dev)
if (sm_register_device(mtd, dev->sm))
goto error1;
- if (device_create_file(&mtd->dev, &dev_attr_media_type)) {
+ if (device_create_file(mtd->dev, &dev_attr_media_type)) {
message("can't create media type sysfs attribute");
goto error3;
}
@@ -672,7 +672,7 @@ static void r852_unregister_nand_device(struct r852_device *dev)
if (!dev->card_registered)
return;
- device_remove_file(&mtd->dev, &dev_attr_media_type);
+ device_remove_file(mtd->dev, &dev_attr_media_type);
WARN_ON(mtd_device_unregister(mtd));
nand_cleanup(dev->chip);
r852_engine_disable(dev);
diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c
index 201dd62b9990..135cdb67a472 100644
--- a/drivers/mtd/nand/raw/renesas-nand-controller.c
+++ b/drivers/mtd/nand/raw/renesas-nand-controller.c
@@ -1254,7 +1254,7 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
nand_set_flash_node(chip, np);
mtd = nand_to_mtd(chip);
- mtd->dev.parent = rnandc->dev;
+ mtd->parent_dev = rnandc->dev;
if (!mtd->name) {
dev_err(rnandc->dev, "Missing MTD label\n");
return -EINVAL;
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index 9444ba02696d..1e56e5e41f79 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -1028,7 +1028,7 @@ static int rk_nfc_ecc_init(struct device *dev, struct mtd_info *mtd)
static int rk_nfc_attach_chip(struct nand_chip *chip)
{
struct mtd_info *mtd = nand_to_mtd(chip);
- struct device *dev = mtd->dev.parent;
+ struct device *dev = mtd->parent_dev;
struct rk_nfc *nfc = nand_get_controller_data(chip);
struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip);
struct nand_ecc_ctrl *ecc = &chip->ecc;
@@ -1175,7 +1175,7 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
mtd = nand_to_mtd(chip);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
if (!mtd->name) {
dev_err(nfc->dev, "NAND label property is mandatory\n");
diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c
index 97f733e481ff..36ba5b3da3a0 100644
--- a/drivers/mtd/nand/raw/sh_flctl.c
+++ b/drivers/mtd/nand/raw/sh_flctl.c
@@ -1153,7 +1153,7 @@ static int flctl_probe(struct platform_device *pdev)
nand = &flctl->chip;
flctl_mtd = nand_to_mtd(nand);
nand_set_flash_node(nand, pdev->dev.of_node);
- flctl_mtd->dev.parent = &pdev->dev;
+ flctl_mtd->parent_dev = &pdev->dev;
flctl->pdev = pdev;
flctl->hwecc = pdata->has_hwecc;
flctl->holden = pdata->use_holden;
diff --git a/drivers/mtd/nand/raw/sharpsl.c b/drivers/mtd/nand/raw/sharpsl.c
index 4154ab74f169..5aea5afd480e 100644
--- a/drivers/mtd/nand/raw/sharpsl.c
+++ b/drivers/mtd/nand/raw/sharpsl.c
@@ -160,7 +160,7 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
/* Link the private data with the MTD structure */
mtd = nand_to_mtd(this);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
mtd_set_ooblayout(mtd, data->ecc_layout);
platform_set_drvdata(pdev, sharpsl);
diff --git a/drivers/mtd/nand/raw/socrates_nand.c b/drivers/mtd/nand/raw/socrates_nand.c
index 668584683ce5..91d4394c1137 100644
--- a/drivers/mtd/nand/raw/socrates_nand.c
+++ b/drivers/mtd/nand/raw/socrates_nand.c
@@ -164,7 +164,7 @@ static int socrates_nand_probe(struct platform_device *ofdev)
nand_set_controller_data(nand_chip, host);
nand_set_flash_node(nand_chip, ofdev->dev.of_node);
mtd->name = "socrates_nand";
- mtd->dev.parent = &ofdev->dev;
+ mtd->parent_dev = &ofdev->dev;
nand_chip->legacy.cmd_ctrl = socrates_nand_cmd_ctrl;
nand_chip->legacy.read_byte = socrates_nand_read_byte;
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index 9f42f8ab3212..78af58d87960 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -2011,7 +2011,7 @@ static int stm32_fmc2_nfc_probe(struct platform_device *pdev)
nand = &nfc->nand;
chip = &nand->chip;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
chip->controller = &nfc->base;
chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE |
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index f41feebe4257..4fcb7977de02 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -2514,7 +2514,7 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
nand_set_flash_node(nand, np);
mtd = nand_to_mtd(nand);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
ret = nand_scan(nand, nsels);
if (ret)
diff --git a/drivers/mtd/nand/raw/technologic-nand-controller.c b/drivers/mtd/nand/raw/technologic-nand-controller.c
index a3294aaf43bd..a4de53f954e5 100644
--- a/drivers/mtd/nand/raw/technologic-nand-controller.c
+++ b/drivers/mtd/nand/raw/technologic-nand-controller.c
@@ -160,7 +160,7 @@ static int ts72xx_nand_probe(struct platform_device *pdev)
nand_set_flash_node(&data->chip, to_of_node(child));
mtd = nand_to_mtd(&data->chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
platform_set_drvdata(pdev, data);
/*
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
index 1104d75b6cbe..ef765fa21e72 100644
--- a/drivers/mtd/nand/raw/tegra_nand.c
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -1115,7 +1115,7 @@ static int tegra_nand_chips_init(struct device *dev,
mtd = nand_to_mtd(chip);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
mtd->owner = THIS_MODULE;
nand_set_flash_node(chip, np_nand);
diff --git a/drivers/mtd/nand/raw/txx9ndfmc.c b/drivers/mtd/nand/raw/txx9ndfmc.c
index 4cc6e91dbc23..6fd6e530c38b 100644
--- a/drivers/mtd/nand/raw/txx9ndfmc.c
+++ b/drivers/mtd/nand/raw/txx9ndfmc.c
@@ -324,7 +324,7 @@ static int txx9ndfmc_probe(struct platform_device *dev)
continue;
chip = &txx9_priv->chip;
mtd = nand_to_mtd(chip);
- mtd->dev.parent = &dev->dev;
+ mtd->parent_dev = &dev->dev;
chip->legacy.read_byte = txx9ndfmc_read_byte;
chip->legacy.read_buf = txx9ndfmc_read_buf;
diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index 9940681810cf..57da9eab940e 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -822,7 +822,7 @@ static int vf610_nfc_probe(struct platform_device *pdev)
mtd = nand_to_mtd(chip);
mtd->owner = THIS_MODULE;
- mtd->dev.parent = nfc->dev;
+ mtd->parent_dev = nfc->dev;
mtd->name = DRV_NAME;
irq = platform_get_irq(pdev, 0);
diff --git a/drivers/mtd/nand/raw/xway_nand.c b/drivers/mtd/nand/raw/xway_nand.c
index af84395dc66e..7fcefb24fc23 100644
--- a/drivers/mtd/nand/raw/xway_nand.c
+++ b/drivers/mtd/nand/raw/xway_nand.c
@@ -182,7 +182,7 @@ static int xway_nand_probe(struct platform_device *pdev)
nand_set_flash_node(&data->chip, pdev->dev.of_node);
mtd = nand_to_mtd(&data->chip);
- mtd->dev.parent = &pdev->dev;
+ mtd->parent_dev = &pdev->dev;
data->chip.legacy.cmd_ctrl = xway_cmd_ctrl;
data->chip.legacy.dev_ready = xway_dev_ready;
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 86a8d759682b..ff86b619c003 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -2046,7 +2046,7 @@ static int spinand_probe(struct spi_mem *mem)
spinand_set_of_node(spinand, mem->spi->dev.of_node);
mutex_init(&spinand->lock);
mtd = spinand_to_mtd(spinand);
- mtd->dev.parent = &mem->spi->dev;
+ mtd->parent_dev = &mem->spi->dev;
ret = spinand_init(spinand);
if (ret)
diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c
index 8607c2fdedcf..0c08cfdc85e3 100644
--- a/drivers/mtd/nand/spi/esmt.c
+++ b/drivers/mtd/nand/spi/esmt.c
@@ -165,7 +165,7 @@ static int f50l1g41lb_otp_lock(struct spinand_device *spinand, loff_t from,
out:
if (spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK, 0)) {
- dev_warn(&spinand_to_mtd(spinand)->dev,
+ dev_warn(spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c
index 36f6cbbd7462..888b9cff46ab 100644
--- a/drivers/mtd/nand/spi/micron.c
+++ b/drivers/mtd/nand/spi/micron.c
@@ -195,7 +195,7 @@ static int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand)
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
0)) {
- dev_warn(&spinand_to_mtd(spinand)->dev,
+ dev_warn(spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
@@ -279,7 +279,7 @@ static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from,
out:
if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, 0)) {
- dev_warn(&spinand_to_mtd(spinand)->dev,
+ dev_warn(spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
diff --git a/drivers/mtd/nand/spi/otp.c b/drivers/mtd/nand/spi/otp.c
index ce41bca86ea9..168bbfdc5ad8 100644
--- a/drivers/mtd/nand/spi/otp.c
+++ b/drivers/mtd/nand/spi/otp.c
@@ -116,7 +116,7 @@ static int spinand_otp_rw(struct spinand_device *spinand, loff_t ofs,
*retlen = copied;
if (spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0)) {
- dev_warn(&spinand_to_mtd(spinand)->dev,
+ dev_warn(spinand_to_mtd(spinand)->dev,
"Can not disable OTP mode\n");
ret = -EIO;
}
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..0f1005b96ccf 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3579,7 +3579,7 @@ static int spi_nor_set_mtd_info(struct spi_nor *nor)
spi_nor_set_mtd_locking_ops(nor);
spi_nor_set_mtd_otp_ops(nor);
- mtd->dev.parent = dev;
+ mtd->parent_dev = dev;
if (!mtd->name)
mtd->name = dev_name(dev);
mtd->type = MTD_NORFLASH;
diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 910e6b0ff5e7..d1468a9a8917 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -951,7 +951,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
ubi->dev.release = dev_release;
ubi->dev.class = &ubi_class;
ubi->dev.groups = ubi_dev_groups;
- ubi->dev.parent = &mtd->dev;
+ ubi->dev.parent = mtd->dev;
ubi->mtd = mtd;
ubi->ubi_num = ubi_num;
diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h
index 745655e0d462..7c95d2ee4615 100644
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -225,6 +225,8 @@ 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: core callback freeing data after its last lifetime reference
*
* 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 +237,8 @@ struct mtd_master {
struct mutex chrdev_lock;
unsigned int suspended : 1;
bool unregistering;
+ struct kref life_refcnt;
+ void (*release)(struct mtd_info *mtd);
};
struct mtd_info {
@@ -380,8 +384,15 @@ struct mtd_info {
void *priv;
struct module *owner;
- struct device dev;
+ struct device *dev;
+ struct device *parent_dev;
+ struct device_node *of_node;
struct kref refcnt;
+ struct kref data_refcnt;
+ /* Protected by mtd_table_mutex; false after del_mtd_device(). */
+ bool registered;
+ /* Lifetime reference held until the MTD metadata is released. */
+ struct mtd_info *ref_master;
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;
struct nvmem_device *otp_user_nvmem;
@@ -463,14 +474,14 @@ static inline void mtd_set_pairing_scheme(struct mtd_info *mtd,
static inline void mtd_set_of_node(struct mtd_info *mtd,
struct device_node *np)
{
- mtd->dev.of_node = np;
+ mtd->of_node = np;
if (!mtd->name)
of_property_read_string(np, "label", &mtd->name);
}
static inline struct device_node *mtd_get_of_node(struct mtd_info *mtd)
{
- return dev_of_node(&mtd->dev);
+ return mtd->of_node;
}
static inline u32 mtd_oobavail(struct mtd_info *mtd, struct mtd_oob_ops *ops)
@@ -683,6 +694,9 @@ extern int mtd_device_parse_register(struct mtd_info *mtd,
#define mtd_device_register(master, parts, nr_parts) \
mtd_device_parse_register(master, NULL, NULL, parts, nr_parts)
extern int mtd_device_unregister(struct mtd_info *master);
+void mtd_set_release(struct mtd_info *master,
+ void (*release)(struct mtd_info *mtd));
+void mtd_put_master(struct mtd_info *master);
extern struct mtd_info *get_mtd_device(struct mtd_info *mtd, int num);
struct mtd_info *get_mtd_device_by_dev(struct device *dev);
extern int __get_mtd_device(struct mtd_info *mtd);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 04/58] mtd: retain virtual concatenations across component removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (2 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 03/58] mtd: separate class devices from retained backing objects James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 3:09 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 05/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
` (53 subsequent siblings)
57 siblings, 1 reply; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Yuqiang Sun, Amit Kumar Mahapatra, Luca Ceresoli,
Bernhard Frauendienst, Gustavo A. R. Silva, Kees Cook,
linux-hardening
Virtual concatenation keeps hidden partition objects outside the normal
partition list. They neither retain their backing masters nor get removed
by registration-failure cleanup. Teardown also compares a master against
its hidden children, leaving stale pointers when a component disappears.
Give staged components ownership of managed backing objects and register
the allocation returned by mtd_concat_create() directly. Serialize staging
and publication across controllers, keeping components in DT order and
publishing only after every contributing master finishes registration.
Disconnect and drain the concatenation before releasing any component.
Gate all forwarding operations, including OOB-layout callbacks, with SRCU.
Use its NMI-safe reader flavor because panic writes use the same object.
Drain while the staging lock still serializes concurrent removals, so a
second unmanaged master cannot free its state before admitted I/O finishes.
Then unregister outside the staging lock and release component references.
An old handle retains disconnected virtual metadata, not a replacement's
components or hardware callbacks.
Unwind hidden components with ordinary partitions on registration failure,
preserving other masters' staged components for a later probe. Advance the
partition offset past hidden components, and use a separate partition-lock
class for virtual masters to allow removal through a physical 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/Kconfig | 1 +
drivers/mtd/mtd_virt_concat.c | 526 +++++++++++++++++++++---------------------
drivers/mtd/mtdconcat.c | 103 ++++++++-
drivers/mtd/mtdcore.c | 3 +-
drivers/mtd/mtdpart.c | 15 +-
include/linux/mtd/concat.h | 22 +-
6 files changed, 400 insertions(+), 270 deletions(-)
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index d13a065874ba..97f10fda30a4 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -4,6 +4,7 @@ config MTD_DEVICE
menuconfig MTD
tristate "Memory Technology Device (MTD) support"
select MTD_DEVICE
+ select SRCU
imply NVMEM
help
Memory Technology Devices are flash, RAM and similar chips, often
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
index 0d501e1565c0..2738e7417e2d 100644
--- a/drivers/mtd/mtd_virt_concat.c
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -7,346 +7,356 @@
*/
#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->ref_master;
+ struct mtd_info *parent = mtd->parent;
+
+ of_node_put(mtd_get_of_node(mtd));
+ release_mtd_partition(mtd);
+ if (master) {
+ if (parent != master)
+ mtd_put_data(parent);
+ 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]);
+ if (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) {
+ struct mtd_concat *concat = container_of(device->mtd, struct mtd_concat, mtd);
+ int i;
+
+ list_del(&device->node);
+ mtd_device_unregister(device->mtd);
+ for (i = 0; i < concat->num_subdev; i++) {
+ mtd_virt_concat_part_put(concat->subdev[i]);
+ concat->subdev[i] = NULL;
}
+ 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_get_of_node(mtd)) {
- 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 (mtd_get_of_node(child) != mtd_get_of_node(mtd)) {
- 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) {
+ mtd_concat_disconnect(item->device->mtd);
+ 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;
-
- for (i = 0; i < (count - 1); i++) {
- if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i)))
- return 0;
- }
+ unsigned int i, j;
- 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++)
- item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1));
+ for (i = 1; i < count; i++) {
+ struct device_node *part = of_parse_phandle(node, 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) {
+ mtd_concat_disconnect(item->device->mtd);
+ 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_info *master = mtd_get_master(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_get_of_node(mtd)) {
- 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. */
+ if (master->master.release) {
+ mtd_get_lifetime(master);
+ mtd->ref_master = master;
+ if (mtd->parent != master)
+ mtd_get_data(mtd->parent);
}
+ 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->parent_dev = parts[0]->parent_dev;
+ mtd_set_of_node(mtd, mtd_get_of_node(parts[0]));
+ 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.parent_dev = concat->subdev[0]->parent_dev;
- mtd_set_of_node(&concat->mtd, mtd_get_of_node(concat->subdev[0]));
-
- /* 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/mtdconcat.c b/drivers/mtd/mtdconcat.c
index c97167d51fe2..cce3e2fcafc8 100644
--- a/drivers/mtd/mtdconcat.c
+++ b/drivers/mtd/mtdconcat.c
@@ -46,6 +46,10 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len,
int ret = 0, err;
int i;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
size_t size, retsize;
@@ -97,6 +101,11 @@ concat_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
struct mtd_concat *concat = CONCAT(mtd);
int err = -EINVAL;
int i;
+
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
size_t size, retsize;
@@ -139,6 +148,10 @@ concat_write(struct mtd_info *mtd, loff_t to, size_t len,
int err = -EINVAL;
int i;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
size_t size, retsize;
@@ -180,6 +193,10 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs,
int i;
int err = -EINVAL;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
/* Calculate total length of data */
for (i = 0; i < count; i++)
total_len += vecs[i].iov_len;
@@ -251,6 +268,10 @@ concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
struct mtd_oob_ops devops = *ops;
int i, err, ret = 0;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
ops->retlen = ops->oobretlen = 0;
for (i = 0; i < concat->num_subdev; i++) {
@@ -308,6 +329,10 @@ concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
struct mtd_oob_ops devops = *ops;
int i, err;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
if (!(mtd->flags & MTD_WRITEABLE))
return -EROFS;
@@ -356,6 +381,10 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
uint64_t length, offset = 0;
struct erase_info *erase;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
/*
* Check for proper erase block alignment of the to-be-erased area.
* It is easier to do this based on the super device's erase
@@ -472,6 +501,10 @@ static int concat_xxlock(struct mtd_info *mtd, loff_t ofs, uint64_t len,
struct mtd_concat *concat = CONCAT(mtd);
int i, err = -EINVAL;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
uint64_t size;
@@ -519,6 +552,10 @@ static int concat_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
struct mtd_concat *concat = CONCAT(mtd);
int i, err = -EINVAL;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
@@ -541,6 +578,10 @@ static void concat_sync(struct mtd_info *mtd)
struct mtd_concat *concat = CONCAT(mtd);
int i;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
mtd_sync(subdev);
@@ -552,6 +593,10 @@ static int concat_suspend(struct mtd_info *mtd)
struct mtd_concat *concat = CONCAT(mtd);
int i, rc = 0;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return 0;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
if ((rc = mtd_suspend(subdev)) < 0)
@@ -565,6 +610,10 @@ static void concat_resume(struct mtd_info *mtd)
struct mtd_concat *concat = CONCAT(mtd);
int i;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
mtd_resume(subdev);
@@ -576,6 +625,10 @@ static int concat_block_isbad(struct mtd_info *mtd, loff_t ofs)
struct mtd_concat *concat = CONCAT(mtd);
int i, res = 0;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
if (!mtd_can_have_bb(concat->subdev[0]))
return res;
@@ -599,6 +652,10 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
struct mtd_concat *concat = CONCAT(mtd);
int i, err = -EINVAL;
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+
for (i = 0; i < concat->num_subdev; i++) {
struct mtd_info *subdev = concat->subdev[i];
@@ -616,6 +673,33 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
return err;
}
+static int concat_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+ return mtd_ooblayout_ecc(concat->subdev[0], section, region);
+}
+
+static int concat_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+
+ guard(srcu_fast)(&concat->srcu);
+ if (READ_ONCE(concat->disconnected))
+ return -ENODEV;
+ return mtd_ooblayout_free(concat->subdev[0], section, region);
+}
+
+static const struct mtd_ooblayout_ops concat_ooblayout_ops = {
+ .ecc = concat_ooblayout_ecc,
+ .free = concat_ooblayout_free,
+};
+
/*
* This function constructs a virtual MTD device by concatenating
* num_devs MTD devices. A pointer to the new device object is
@@ -738,7 +822,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
}
- mtd_set_ooblayout(&concat->mtd, subdev[0]->ooblayout);
+ if (subdev[0]->ooblayout)
+ mtd_set_ooblayout(&concat->mtd, &concat_ooblayout_ops);
concat->num_subdev = num_devs;
concat->mtd.name = name;
@@ -877,13 +962,29 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
erase_region_p->numblocks = tmp64;
}
+ if (init_srcu_struct_fast(&concat->srcu)) {
+ kfree(concat->mtd.eraseregions);
+ kfree(concat);
+ return NULL;
+ }
return &concat->mtd;
}
+void mtd_concat_disconnect(struct mtd_info *mtd)
+{
+ struct mtd_concat *concat = CONCAT(mtd);
+
+ WRITE_ONCE(concat->disconnected, true);
+ synchronize_srcu(&concat->srcu);
+}
+EXPORT_SYMBOL_GPL(mtd_concat_disconnect);
+
/* Cleans the context obtained from mtd_concat_create() */
void mtd_concat_destroy(struct mtd_info *mtd)
{
struct mtd_concat *concat = CONCAT(mtd);
+
+ cleanup_srcu_struct(&concat->srcu);
if (concat->mtd.numeraseregions)
kfree(concat->mtd.eraseregions);
kfree(concat);
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 5793c865f3a2..f5f7a876766b 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1207,7 +1207,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;
}
@@ -1228,6 +1228,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/mtdpart.c b/drivers/mtd/mtdpart.c
index eb7704a5d714..396d5c40d351 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -93,6 +93,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
child->parent = parent;
child->part.offset = part->offset;
INIT_LIST_HEAD(&child->partitions);
+ INIT_LIST_HEAD(&child->part.node);
if (child->part.offset == MTDPART_OFS_APPEND)
child->part.offset = cur_offset;
@@ -323,6 +324,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 +378,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 +432,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..af30d338fee4 100644
--- a/include/linux/mtd/concat.h
+++ b/include/linux/mtd/concat.h
@@ -8,6 +8,7 @@
#ifndef MTD_CONCAT_H
#define MTD_CONCAT_H
+#include <linux/srcu.h>
/*
* Our storage structure:
@@ -17,6 +18,8 @@
*/
struct mtd_concat {
struct mtd_info mtd;
+ struct srcu_struct srcu;
+ bool disconnected;
int num_subdev;
struct mtd_info *subdev[];
};
@@ -27,6 +30,7 @@ struct mtd_info *mtd_concat_create(
const char *name); /* name for the new device */
void mtd_concat_destroy(struct mtd_info *mtd);
+void mtd_concat_disconnect(struct mtd_info *mtd);
/**
* mtd_virt_concat_node_create - Create a component for concatenation
@@ -43,23 +47,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 +72,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 disconnected concat metadata.
*
*/
int mtd_virt_concat_destroy(struct mtd_info *mtd);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 05/58] mtd: rawnand: separate hardware disconnection from cleanup
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (3 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 04/58] mtd: retain virtual concatenations across component removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 06/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
` (52 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Disconnection does not free the chip or controller allocation. Their
storage must remain valid while existing MTD handles can enter the
disconnection check.
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 f6a054919c06..720da4174833 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 7169c014d197..cd0d7d20a1d6 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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 06/58] mtd: rawnand: share retained chip and controller ownership
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (4 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 05/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
` (51 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Tze Yee Ng
Retained MTD handles need chip locks and immutable metadata after their
controller is removed. Run NAND cleanup during disconnection, while
manufacturer, ECC and controller callbacks are still available. Leave
only core-owned allocation release for the final MTD put.
Copy the chip name and snapshot its OOB layout before publication.
Retained layout queries then use NAND-core callbacks and copied regions,
without touching driver-owned tables or working buffers freed by cleanup.
Use the NAND core as the MTD operation owner so an open handle does not
keep the hardware driver loaded.
Provide reference-counted controller storage with an initial managed
reference, acquired before hardware resources. Each chip holds another
reference, allowing devres teardown and the last chip put to release the
allocation independently. Support separate chips and chips embedded in a
shared allocation without controller-specific final-release callbacks.
Serialize complete bad-block marking with cleanup: its BBM and BBT steps
drop the chip lock between operations. Disconnect and wake suspended
waiters before waiting for that sequence to finish. Protect reserved-block
queries and secured-region checks before freeing their backing tables.
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_base.c | 29 ++++-
drivers/mtd/nand/raw/nand_lifetime.c | 246 +++++++++++++++++++++++++++++++++++
include/linux/mtd/rawnand.h | 28 ++++
4 files changed, 297 insertions(+), 7 deletions(-)
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_base.c b/drivers/mtd/nand/raw/nand_base.c
index 720da4174833..dec9615c2900 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -363,8 +363,8 @@ int nand_get_device(struct nand_chip *chip)
* 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.
+ * For chips with retained storage, run NAND cleanup before returning. Existing
+ * handles retain the disconnected chip and a core-owned OOB layout snapshot.
*/
void nand_disconnect(struct nand_chip *chip)
{
@@ -374,6 +374,11 @@ void nand_disconnect(struct nand_chip *chip)
mutex_unlock(&chip->controller->lock);
mutex_unlock(&chip->lock);
wake_up_all(&chip->resume_wq);
+ if (chip->lifetime) {
+ guard(mutex)(&chip->markbad_lock);
+
+ nand_cleanup(chip);
+ }
}
EXPORT_SYMBOL_GPL(nand_disconnect);
@@ -639,6 +644,9 @@ static int nand_block_isreserved(struct mtd_info *mtd, loff_t ofs)
{
struct nand_chip *chip = mtd_to_nand(mtd);
+ guard(mutex)(&chip->lock);
+ if (chip->disconnected)
+ return -ENODEV;
if (!chip->bbt)
return 0;
/* Return info from the table */
@@ -4569,15 +4577,17 @@ int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr,
if (check_offs_len(chip, instr->addr, instr->len))
return -EINVAL;
- /* Check if the region is secured */
- if (nand_region_is_secured(chip, instr->addr, instr->len))
- return -EIO;
-
/* Grab the lock and see if the device is available */
ret = nand_get_device(chip);
if (ret)
return ret;
+ /* Check if the region is secured */
+ if (nand_region_is_secured(chip, instr->addr, instr->len)) {
+ nand_release_device(chip);
+ return -EIO;
+ }
+
/* Shift to get first page */
page = (int)(instr->addr >> chip->page_shift);
chipnr = (int)(instr->addr >> chip->chip_shift);
@@ -4703,8 +4713,12 @@ static int nand_block_isbad(struct mtd_info *mtd, loff_t offs)
*/
static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
+ struct nand_chip *chip = mtd_to_nand(mtd);
int ret;
+ /* The BBM and BBT updates acquire the chip lock separately. */
+ guard(mutex)(&chip->markbad_lock);
+
ret = nand_block_isbad(mtd, ofs);
if (ret) {
/* If it was bad already, return success and do nothing */
@@ -4713,7 +4727,7 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
return ret;
}
- return nand_block_markbad_lowlevel(mtd_to_nand(mtd), ofs);
+ return nand_block_markbad_lowlevel(chip, ofs);
}
/**
@@ -5617,6 +5631,7 @@ static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips,
chip->cur_cs = -1;
mutex_init(&chip->lock);
+ mutex_init(&chip->markbad_lock);
init_waitqueue_head(&chip->resume_wq);
/* Enforce the right timings for reset/detection */
diff --git a/drivers/mtd/nand/raw/nand_lifetime.c b/drivers/mtd/nand/raw/nand_lifetime.c
new file mode 100644
index 000000000000..f2746ef9ea65
--- /dev/null
+++ b/drivers/mtd/nand/raw/nand_lifetime.c
@@ -0,0 +1,246 @@
+// 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>
+
+struct nand_lifetime {
+ void *allocation;
+ struct nand_storage *storage;
+ unsigned int ecc_sections;
+ unsigned int free_sections;
+ int ecc_end;
+ int free_end;
+ struct mtd_oob_region regions[];
+};
+
+static void nand_storage_release(struct kref *ref)
+{
+ struct nand_storage *storage = container_of(ref, struct nand_storage, refcnt);
+
+ kfree(storage->allocation);
+}
+
+/**
+ * nand_storage_init - initialize shared allocation ownership
+ * @storage: reference count embedded in the allocation
+ * @allocation: allocation to free with kfree() after the final put
+ */
+void nand_storage_init(struct nand_storage *storage, void *allocation)
+{
+ kref_init(&storage->refcnt);
+ storage->allocation = allocation;
+}
+EXPORT_SYMBOL_GPL(nand_storage_init);
+
+void nand_storage_get(struct nand_storage *storage)
+{
+ kref_get(&storage->refcnt);
+}
+EXPORT_SYMBOL_GPL(nand_storage_get);
+
+void nand_storage_put(void *data)
+{
+ struct nand_storage *storage = data;
+
+ kref_put(&storage->refcnt, nand_storage_release);
+}
+EXPORT_SYMBOL_GPL(nand_storage_put);
+
+/**
+ * devm_nand_storage_init - retain an allocation through managed teardown
+ * @dev: device whose managed resources use the allocation
+ * @storage: reference count embedded in the allocation
+ * @allocation: allocation to free with kfree() after the final put
+ *
+ * Call before acquiring resources that use the allocation. Takes ownership
+ * even on failure. Chips can hold additional references after driver unbind.
+ */
+int devm_nand_storage_init(struct device *dev, struct nand_storage *storage,
+ void *allocation)
+{
+ nand_storage_init(storage, allocation);
+ return devm_add_action_or_reset(dev, nand_storage_put, storage);
+}
+EXPORT_SYMBOL_GPL(devm_nand_storage_init);
+
+/**
+ * devm_nand_controller_init - initialize retained controller storage
+ * @dev: controller device
+ * @controller: controller embedded in non-devm storage
+ * @allocation: allocation containing the controller, freed with kfree()
+ *
+ * Call before acquiring managed hardware resources. Device teardown drops its
+ * reference after releasing those resources. Takes ownership even on failure.
+ */
+int devm_nand_controller_init(struct device *dev, struct nand_controller *controller,
+ void *allocation)
+{
+ nand_controller_init(controller);
+ return devm_nand_storage_init(dev, &controller->storage, allocation);
+}
+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 int nand_retained_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct nand_lifetime *lifetime = mtd_to_nand(mtd)->lifetime;
+
+ if (section >= lifetime->ecc_sections)
+ return lifetime->ecc_end;
+ *region = lifetime->regions[section];
+ return 0;
+}
+
+static int nand_retained_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ struct nand_lifetime *lifetime = mtd_to_nand(mtd)->lifetime;
+
+ if (section >= lifetime->free_sections)
+ return lifetime->free_end;
+ *region = lifetime->regions[lifetime->ecc_sections + section];
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops nand_retained_ooblayout = {
+ .ecc = nand_retained_ooblayout_ecc,
+ .free = nand_retained_ooblayout_free,
+};
+
+static void nand_mtd_release(struct mtd_info *mtd)
+{
+ struct nand_lifetime *lifetime = mtd_to_nand(mtd)->lifetime;
+ struct nand_storage *storage = lifetime->storage;
+ struct device *parent = mtd->parent_dev;
+
+ of_node_put(mtd_get_of_node(mtd));
+ kfree(mtd->name);
+ kfree(lifetime->allocation);
+ kfree(lifetime);
+ if (storage)
+ nand_storage_put(storage);
+ put_device(parent);
+}
+
+static int nand_prepare_lifetime(struct nand_chip *chip, void *allocation,
+ struct nand_storage *storage)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct nand_lifetime *lifetime;
+ struct mtd_oob_region region;
+ unsigned int ecc_sections, free_sections;
+ const char *name = mtd->name;
+ int ecc_end, free_end, ret;
+ char *copy;
+
+ /* Keep layout queries independent of controller-owned code and data. */
+ for (ecc_sections = 0; ; ecc_sections++) {
+ ecc_end = mtd_ooblayout_ecc(mtd, ecc_sections, ®ion);
+ if (ecc_end)
+ break;
+ }
+ if (ecc_end != -ERANGE && ecc_end != -ENOTSUPP)
+ return ecc_end;
+ for (free_sections = 0; ; free_sections++) {
+ free_end = mtd_ooblayout_free(mtd, free_sections, ®ion);
+ if (free_end)
+ break;
+ }
+ if (free_end != -ERANGE && free_end != -ENOTSUPP)
+ return free_end;
+
+ lifetime = kzalloc(struct_size(lifetime, regions, ecc_sections + free_sections),
+ GFP_KERNEL);
+ if (!lifetime)
+ return -ENOMEM;
+ for (unsigned int i = 0; i < ecc_sections; i++) {
+ ret = mtd_ooblayout_ecc(mtd, i, &lifetime->regions[i]);
+ if (ret)
+ goto free_lifetime;
+ }
+ for (unsigned int i = 0; i < free_sections; i++) {
+ ret = mtd_ooblayout_free(mtd, i, &lifetime->regions[ecc_sections + i]);
+ if (ret)
+ goto free_lifetime;
+ }
+
+ if (!name)
+ name = dev_name(mtd->parent_dev);
+ copy = kstrdup(name, GFP_KERNEL);
+ if (!copy) {
+ ret = -ENOMEM;
+ goto free_lifetime;
+ }
+
+ lifetime->allocation = allocation;
+ lifetime->storage = storage;
+ lifetime->ecc_sections = ecc_sections;
+ lifetime->free_sections = free_sections;
+ lifetime->ecc_end = ecc_end;
+ lifetime->free_end = free_end;
+ chip->lifetime = lifetime;
+ mtd->name = copy;
+ mtd->owner = THIS_MODULE;
+ mtd->_get_device = nand_mtd_get_device;
+ mtd_set_ooblayout(mtd, &nand_retained_ooblayout);
+ get_device(mtd->parent_dev);
+ of_node_get(nand_get_flash_node(chip));
+ if (storage)
+ nand_storage_get(storage);
+ mtd_set_release(mtd, nand_mtd_release);
+
+ return 0;
+
+free_lifetime:
+ kfree(lifetime);
+ return ret;
+}
+
+/**
+ * nand_set_lifetime - retain a scanned chip until its final MTD reference
+ * @chip: successfully scanned, unpublished NAND chip
+ * @allocation: chip allocation, or NULL if embedded in retained controller storage
+ *
+ * Copies the name and OOB layout and retains the parent, flash node and managed
+ * controller allocation. On success, unregister MTD, call nand_disconnect() to
+ * drain operations and run NAND cleanup, then call mtd_put_master(). Use the
+ * same disconnect/put sequence on registration failure. Final release only
+ * frees data in the NAND core; it never calls back into the controller driver.
+ *
+ * Return: zero on success or a negative error, leaving ownership unchanged.
+ */
+int nand_set_lifetime(struct nand_chip *chip, void *allocation)
+{
+ struct nand_storage *storage = &chip->controller->storage;
+
+ if (!storage->allocation)
+ storage = NULL;
+ return nand_prepare_lifetime(chip, allocation, storage);
+}
+EXPORT_SYMBOL_GPL(nand_set_lifetime);
+
+/**
+ * nand_set_shared_lifetime - retain a chip embedded in a shared allocation
+ * @chip: successfully scanned, unpublished NAND chip
+ * @storage: shared allocation containing the chip
+ *
+ * Like nand_set_lifetime(), but for a chip whose allocation is independent of
+ * its controller, and must also outlive other device-managed resources.
+ */
+int nand_set_shared_lifetime(struct nand_chip *chip, struct nand_storage *storage)
+{
+ return nand_prepare_lifetime(chip, NULL, storage);
+}
+EXPORT_SYMBOL_GPL(nand_set_shared_lifetime);
diff --git a/include/linux/mtd/rawnand.h b/include/linux/mtd/rawnand.h
index 90b2169cc745..e6b78d0f2deb 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;
@@ -1104,6 +1105,22 @@ struct nand_controller_ops {
const struct nand_interface_config *conf);
};
+/**
+ * struct nand_storage - allocation retained by disconnected NAND devices
+ * @refcnt: references to the allocation
+ * @allocation: allocation freed by the NAND core after the final put
+ */
+struct nand_storage {
+ struct kref refcnt;
+ void *allocation;
+};
+
+void nand_storage_init(struct nand_storage *storage, void *allocation);
+void nand_storage_get(struct nand_storage *storage);
+void nand_storage_put(void *storage);
+int devm_nand_storage_init(struct device *dev, struct nand_storage *storage,
+ void *allocation);
+
/**
* struct nand_controller - Structure used to describe a NAND controller
*
@@ -1116,6 +1133,7 @@ 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.
+ * @storage: optional retained controller allocation
*/
struct nand_controller {
struct mutex lock;
@@ -1125,6 +1143,7 @@ struct nand_controller {
unsigned int cont_read: 1;
} supported_op;
bool controller_wp;
+ struct nand_storage storage;
};
static inline void nand_controller_init(struct nand_controller *nfc)
@@ -1132,6 +1151,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 *allocation);
+
/**
* struct nand_legacy - NAND chip legacy fields/hooks
* @IO_ADDR_R: address to read the 8 I/O lines of the flash device
@@ -1266,6 +1288,7 @@ struct nand_secure_region {
* @buf_align: Minimum buffer alignment required by a platform
* @lock: Lock protecting the suspended and disconnected fields. Also serializes
* accesses to the NAND device
+ * @markbad_lock: Serializes complete BBM/BBT updates with disconnect cleanup
* @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.
@@ -1285,6 +1308,7 @@ struct nand_secure_region {
* independent devices
* @ecc: The ECC controller structure
* @priv: Chip private data
+ * @lifetime: Core-owned metadata and allocation references for retained handles
*/
struct nand_chip {
struct nand_device base;
@@ -1327,6 +1351,7 @@ struct nand_chip {
/* Internals */
struct mutex lock;
+ struct mutex markbad_lock;
bool suspended;
bool disconnected;
wait_queue_head_t resume_wq;
@@ -1345,6 +1370,7 @@ struct nand_chip {
struct nand_controller *controller;
struct nand_ecc_ctrl ecc;
void *priv;
+ struct nand_lifetime *lifetime;
};
static inline struct nand_chip *mtd_to_nand(struct mtd_info *mtd)
@@ -1598,6 +1624,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_lifetime(struct nand_chip *chip, void *allocation);
+int nand_set_shared_lifetime(struct nand_chip *chip, struct nand_storage *storage);
/*
* External helper for controller drivers that have to implement the WAITRDY
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (5 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 06/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
` (50 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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. Run nand_cleanup() during disconnection, freeing the
per-sector working data while the driver is bound. Retain copied names,
OOB regions, flash-node references and chip storage until the final put.
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 | 44 ++++++++++++++++++++++++++-------------
1 file changed, 30 insertions(+), 14 deletions(-)
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 4fcb7977de02..d175dc685681 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
@@ -2027,9 +2028,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 +2045,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;
@@ -2440,8 +2439,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 +2465,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 +2476,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 +2519,31 @@ 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_lifetime(nand, sunxi_nand);
+ if (ret)
+ goto err_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(&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 +2624,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, nfc);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r);
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (6 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 09/58] mtd: rawnand: gpio: " James Hilliard
` (49 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup while the platform is bound. Keep the chip, controller
lock and copied layout 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 | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/plat_nand.c b/drivers/mtd/nand/raw/plat_nand.c
index a2775886e7c4..7963ca2d4557 100644
--- a/drivers/mtd/nand/raw/plat_nand.c
+++ b/drivers/mtd/nand/raw/plat_nand.c
@@ -65,10 +65,12 @@ 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, data);
+ if (err)
+ return err;
data->ready_gpio = devm_gpiod_get_optional(&pdev->dev, "ready",
GPIOD_IN);
@@ -77,7 +79,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 +127,11 @@ static int plat_nand_probe(struct platform_device *pdev)
goto out;
part_types = pdata->chip.part_probe_types;
+ err = nand_set_lifetime(chip, NULL);
+ if (err) {
+ nand_cleanup(chip);
+ goto out;
+ }
err = mtd_device_parse_register(mtd, part_types, NULL,
pdata->chip.partitions,
@@ -134,7 +140,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 +160,10 @@ static void plat_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
if (pdata->ctrl.remove)
pdata->ctrl.remove(pdev);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id plat_nand_match[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 09/58] mtd: rawnand: gpio: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (7 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
` (48 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/gpio.c b/drivers/mtd/nand/raw/gpio.c
index d17fb2244923..2a8c44865636 100644
--- a/drivers/mtd/nand/raw/gpio.c
+++ b/drivers/mtd/nand/raw/gpio.c
@@ -273,13 +273,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 +295,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, gpiomtd);
+ if (ret)
+ return ret;
chip = &gpiomtd->nand_chip;
@@ -345,7 +349,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 +377,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_lifetime(chip, NULL);
+ if (ret) {
+ nand_cleanup(chip);
+ goto err_wp;
+ }
ret = mtd_device_register(mtd, gpiomtd->plat.parts,
gpiomtd->plat.num_parts);
if (!ret)
return 0;
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
err_wp:
if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp))
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 10/58] mtd: rawnand: stm32_fmc2: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (8 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 09/58] mtd: rawnand: gpio: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
` (47 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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;
run NAND cleanup during disconnection and retain the core-owned layout
and controller storage 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 | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index 78af58d87960..2b8c0b467f7b 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -1915,12 +1915,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, nfc);
+ 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 +2026,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_lifetime(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 +2069,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 +2082,7 @@ static void stm32_fmc2_nfc_remove(struct platform_device *pdev)
sg_free_table(&nfc->dma_ecc_sg);
stm32_fmc2_nfc_wp_enable(nand);
+ mtd_put_master(nand_to_mtd(chip));
}
static int stm32_fmc2_nfc_suspend(struct device *dev)
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 11/58] mtd: rawnand: denali: retain chips across controller removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (9 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 12/58] mtd: rawnand: arasan: " James Hilliard
` (46 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 26 ++++++++++++++++++--------
drivers/mtd/nand/raw/denali_dt.c | 13 ++++++++-----
drivers/mtd/nand/raw/denali_pci.c | 9 ++++++---
3 files changed, 32 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c
index 9c07e4e25a27..098b00724bee 100644
--- a/drivers/mtd/nand/raw/denali.c
+++ b/drivers/mtd/nand/raw/denali.c
@@ -1176,6 +1176,7 @@ static const struct nand_controller_ops denali_controller_ops = {
.setup_interface = denali_setup_interface,
};
+/* Takes ownership of dchip, including on failure. */
int denali_chip_init(struct denali_controller *denali,
struct denali_chip *dchip)
{
@@ -1192,7 +1193,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 +1202,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 +1213,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 +1254,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_lifetime(chip, dchip);
+ 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 +1273,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 +1284,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 +1377,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..9abe607fc7e7 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,7 @@ 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 int denali_dt_probe(struct platform_device *pdev)
@@ -117,10 +117,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, dt);
+ 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..37ef153ad8d8 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"
@@ -35,9 +36,12 @@ 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);
+ if (ret)
+ return ret;
ret = pcim_enable_device(dev);
if (ret) {
@@ -92,8 +96,7 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
nsels = denali->nbanks;
- dchip = devm_kzalloc(denali->dev, struct_size(dchip, sels, nsels),
- GFP_KERNEL);
+ dchip = kzalloc(struct_size(dchip, sels, nsels), GFP_KERNEL);
if (!dchip) {
ret = -ENOMEM;
goto out_remove_denali;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 12/58] mtd: rawnand: arasan: retain chips across controller removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (10 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
` (45 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Boris Brezillon, Michal Simek
Keep chip metadata and controller storage until the last MTD reference
is released. Release BCH state during disconnection. 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 | 47 ++++++++++++++++++---------
1 file changed, 32 insertions(+), 15 deletions(-)
diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c
index 839aef6c460b..a28daae30330 100644
--- a/drivers/mtd/nand/raw/arasan-nand-controller.c
+++ b/drivers/mtd/nand/raw/arasan-nand-controller.c
@@ -1276,7 +1276,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 +1284,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 +1327,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_lifetime(chip, to_anand(chip));
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 +1368,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));
}
}
@@ -1436,12 +1452,14 @@ 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, nfc);
+ 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 +1499,12 @@ static void anfc_remove(struct platform_device *pdev)
int i;
struct arasan_nfc *nfc = platform_get_drvdata(pdev);
+ anfc_chips_cleanup(nfc);
for (i = 0; i < nfc->ncs; i++) {
if (nfc->cs_array[i]) {
gpiod_put(nfc->cs_array[i]);
}
}
-
- anfc_chips_cleanup(nfc);
}
static const struct of_device_id anfc_ids[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 13/58] mtd: rawnand: mxic: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (11 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 12/58] mtd: rawnand: arasan: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 14/58] mtd: rawnand: intel: " James Hilliard
` (44 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard,
Mason Yang
A retained MTD handle must not access controller storage freed by
unbind. Disconnect and clean up NAND operations before disabling clocks.
Retain the copied layout and controller storage 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 | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/mxic_nand.c b/drivers/mtd/nand/raw/mxic_nand.c
index ce9fe27fb4ac..9b666a2c0a8d 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"
@@ -492,10 +493,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, nfc);
+ if (err)
+ return err;
nfc->ps_clk = devm_clk_get(&pdev->dev, "ps");
if (IS_ERR(nfc->ps_clk))
@@ -523,7 +526,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 +543,17 @@ static int mxic_nfc_probe(struct platform_device *pdev)
if (err)
goto fail;
+ err = nand_set_lifetime(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 +571,10 @@ static void mxic_nfc_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
mxic_nfc_clk_disable(nfc);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id mxic_nfc_of_ids[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 14/58] mtd: rawnand: intel: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (12 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 15/58] mtd: rawnand: tegra: " James Hilliard
` (43 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c
index d8071180cc0d..42a1d85b1cd4 100644
--- a/drivers/mtd/nand/raw/intel-nand-controller.c
+++ b/drivers/mtd/nand/raw/intel-nand-controller.c
@@ -585,12 +585,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_host);
+ 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 +694,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_lifetime(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 +726,10 @@ static void ebu_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(&ebu_host->chip));
WARN_ON(ret);
- nand_cleanup(&ebu_host->chip);
+ nand_disconnect(&ebu_host->chip);
ebu_nand_disable(&ebu_host->chip);
ebu_dma_cleanup(ebu_host);
+ mtd_put_master(nand_to_mtd(&ebu_host->chip));
}
static const struct of_device_id ebu_nand_match[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 15/58] mtd: rawnand: tegra: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (13 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 14/58] mtd: rawnand: intel: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
` (42 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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
allocation release to the final core-owned 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 | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c
index ef765fa21e72..4b9416ecbe43 100644
--- a/drivers/mtd/nand/raw/tegra_nand.c
+++ b/drivers/mtd/nand/raw/tegra_nand.c
@@ -1068,9 +1068,7 @@ 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;
int nsels, nchips = of_get_child_count(np);
- struct tegra_nand_chip *nand;
struct mtd_info *mtd;
struct nand_chip *chip;
int ret;
@@ -1081,7 +1079,7 @@ static int tegra_nand_chips_init(struct device *dev,
return -EINVAL;
}
- np_nand = of_get_next_child(np, NULL);
+ struct device_node *np_nand __free(device_node) = of_get_next_child(np, NULL);
nsels = of_property_count_elems_of_size(np_nand, "reg", sizeof(u32));
if (nsels != 1) {
@@ -1096,7 +1094,7 @@ static int tegra_nand_chips_init(struct device *dev,
return ret;
}
- nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL);
+ struct tegra_nand_chip *nand __free(kfree) = kzalloc_obj(*nand);
if (!nand)
return -ENOMEM;
@@ -1116,7 +1114,6 @@ static int tegra_nand_chips_init(struct device *dev,
mtd = nand_to_mtd(chip);
mtd->parent_dev = dev;
- mtd->owner = THIS_MODULE;
nand_set_flash_node(chip, np_nand);
@@ -1130,11 +1127,18 @@ static int tegra_nand_chips_init(struct device *dev,
return ret;
mtd_ooblayout_ecc(mtd, 0, &nand->ecc);
+ ret = nand_set_lifetime(chip, container_of(chip, struct tegra_nand_chip, chip));
+ if (ret) {
+ nand_cleanup(chip);
+ return ret;
+ }
+ retain_and_null_ptr(nand);
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;
}
@@ -1149,13 +1153,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, ctrl);
+ 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 +1241,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 16/58] mtd: rawnand: mtk: retain chips across controller removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (14 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 15/58] mtd: rawnand: tegra: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 17/58] mtd: rawnand: rockchip: " James Hilliard
` (41 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 36 +++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c
index a36a82d10aa7..010f614a2da3 100644
--- a/drivers/mtd/nand/raw/mtk_nand.c
+++ b/drivers/mtd/nand/raw/mtk_nand.c
@@ -1356,8 +1356,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 +1365,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;
@@ -1406,7 +1407,7 @@ static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc,
nand->ecc.read_oob = mtk_nfc_read_oob_std;
mtd = nand_to_mtd(nand);
- mtd->owner = THIS_MODULE;
+
mtd->parent_dev = dev;
mtd->name = MTK_NAME;
mtd_set_ooblayout(mtd, &mtk_nfc_ooblayout_ops);
@@ -1415,18 +1416,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_lifetime(nand, chip);
+ 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 +1452,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));
}
}
@@ -1511,11 +1523,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, nfc);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->controller.ops = &mtk_nfc_controller_ops;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 17/58] mtd: rawnand: rockchip: retain chips across controller removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (15 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 18/58] mtd: rawnand: atmel: " James Hilliard
` (40 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 41 +++++++++++++++++--------
1 file changed, 28 insertions(+), 13 deletions(-)
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index 1e56e5e41f79..1616c9335e38 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -1134,8 +1134,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 +1143,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;
@@ -1174,19 +1175,20 @@ static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc,
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
mtd = nand_to_mtd(chip);
- mtd->owner = THIS_MODULE;
+
mtd->parent_dev = dev;
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 +1199,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_lifetime(chip, container_of(chip, struct rk_nfc_nand_chip, chip));
+ 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 +1231,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));
}
}
@@ -1381,11 +1394,13 @@ static int rk_nfc_probe(struct platform_device *pdev)
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, nfc);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nfc->chips);
nfc->controller.ops = &rk_nfc_controller_ops;
@@ -1449,9 +1464,9 @@ static void rk_nfc_remove(struct platform_device *pdev)
{
struct rk_nfc *nfc = platform_get_drvdata(pdev);
+ rk_nfc_chips_cleanup(nfc);
kfree(nfc->page_buf);
kfree(nfc->oob_buf);
- rk_nfc_chips_cleanup(nfc);
rk_nfc_disable_clks(nfc);
}
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 18/58] mtd: rawnand: atmel: retain chips across controller removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (16 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 17/58] mtd: rawnand: rockchip: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
` (39 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 42 ++++++++++++++++++++--------
1 file changed, 30 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index 0c79d30b9e30..087b7b5fe987 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,7 @@ 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 int
@@ -1749,6 +1751,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 +1760,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_lifetime(chip, to_atmel_nand(chip));
+ 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 +1805,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 +1812,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 +1871,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 +2057,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;
@@ -2360,9 +2372,12 @@ 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, nc);
+ if (ret)
+ return ret;
ret = atmel_nand_controller_init(&nc->base, pdev, caps);
if (ret)
@@ -2439,9 +2454,12 @@ 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, nc);
+ if (ret)
+ return ret;
ret = atmel_nand_controller_init(&nc->base, pdev, caps);
if (ret)
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 19/58] mtd: rawnand: orion: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (17 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 18/58] mtd: rawnand: atmel: " James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:27 ` [PATCH v3 20/58] mtd: rawnand: socrates: " James Hilliard
` (38 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/orion_nand.c b/drivers/mtd/nand/raw/orion_nand.c
index 6cec25425710..1b1cf3b31fb3 100644
--- a/drivers/mtd/nand/raw/orion_nand.c
+++ b/drivers/mtd/nand/raw/orion_nand.c
@@ -106,15 +106,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, info);
+ 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 +186,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_lifetime(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 +209,8 @@ static void orion_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
#ifdef CONFIG_OF
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 20/58] mtd: rawnand: socrates: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (18 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
@ 2026-11-12 1:27 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 21/58] mtd: rawnand: fsl_upm: " James Hilliard
` (37 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:27 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Fixes: 1b578193af3b ("[MTD] [NAND] Add support for NAND on the Socrates board")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/socrates_nand.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/socrates_nand.c b/drivers/mtd/nand/raw/socrates_nand.c
index 91d4394c1137..5b9b99bc1b38 100644
--- a/drivers/mtd/nand/raw/socrates_nand.c
+++ b/drivers/mtd/nand/raw/socrates_nand.c
@@ -142,9 +142,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, host);
+ if (res)
+ return res;
host->io_base = of_iomap(ofdev->dev.of_node, 0);
if (host->io_base == NULL) {
@@ -156,7 +159,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 +190,17 @@ static int socrates_nand_probe(struct platform_device *ofdev)
if (res)
goto out;
+ res = nand_set_lifetime(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 +218,10 @@ static void socrates_nand_remove(struct platform_device *ofdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
iounmap(host->io_base);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id socrates_nand_match[] =
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 21/58] mtd: rawnand: fsl_upm: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (19 preceding siblings ...)
2026-11-12 1:27 ` [PATCH v3 20/58] mtd: rawnand: socrates: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 22/58] mtd: rawnand: pasemi: " James Hilliard
` (36 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Fixes: 5c249c5a57dc ("[MTD] [NAND] FSL UPM NAND driver")
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_upm.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_upm.c b/drivers/mtd/nand/raw/fsl_upm.c
index 28335dedb987..f9e02e355317 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_lifetime(&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;
@@ -169,9 +178,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);
+ 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 +234,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 +255,8 @@ static void fun_remove(struct platform_device *ofdev)
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
}
static const struct of_device_id of_fun_match[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 22/58] mtd: rawnand: pasemi: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (20 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 21/58] mtd: rawnand: fsl_upm: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 23/58] mtd: rawnand: ams-delta: " James Hilliard
` (35 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/pasemi_nand.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/pasemi_nand.c b/drivers/mtd/nand/raw/pasemi_nand.c
index b497e84636c1..0a2d5e5bae07 100644
--- a/drivers/mtd/nand/raw/pasemi_nand.c
+++ b/drivers/mtd/nand/raw/pasemi_nand.c
@@ -122,8 +122,10 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
chip = &ddata->chip;
controller = &ddata->controller;
+ err = devm_nand_controller_init(dev, controller, ddata);
+ if (err)
+ return err;
controller->ops = &pasemi_ops;
- nand_controller_init(controller);
chip->controller = controller;
pasemi_nand_mtd = nand_to_mtd(chip);
@@ -136,7 +138,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 +176,15 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
if (err)
goto out_lpc;
+ err = nand_set_lifetime(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 +198,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 +215,13 @@ static void pasemi_nand_remove(struct platform_device *ofdev)
/* Release resources, unregister device */
ret = mtd_device_unregister(pasemi_nand_mtd);
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
release_region(ddata->lpcctl, 4);
iounmap(chip->legacy.IO_ADDR_R);
- /* Free the MTD device structure */
- kfree(ddata);
+ mtd_put_master(pasemi_nand_mtd);
}
static const struct of_device_id pasemi_nand_match[] =
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 23/58] mtd: rawnand: ams-delta: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (21 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 22/58] mtd: rawnand: pasemi: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 24/58] mtd: rawnand: xway: " James Hilliard
` (34 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/ams-delta.c | 24 ++++++++++++++++--------
1 file changed, 16 insertions(+), 8 deletions(-)
diff --git a/drivers/mtd/nand/raw/ams-delta.c b/drivers/mtd/nand/raw/ams-delta.c
index 1afeb38145cd..af3641550b2c 100644
--- a/drivers/mtd/nand/raw/ams-delta.c
+++ b/drivers/mtd/nand/raw/ams-delta.c
@@ -250,10 +250,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, priv);
+ if (err)
+ return err;
this = &priv->nand_chip;
@@ -355,7 +357,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 +383,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_lifetime(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 +410,14 @@ static void gpio_nand_remove(struct platform_device *pdev)
struct mtd_info *mtd = nand_to_mtd(&priv->nand_chip);
int ret;
- /* Apply write protection */
- gpiod_set_value(priv->gpiod_nwp, 1);
-
/* Unregister device */
ret = mtd_device_unregister(mtd);
WARN_ON(ret);
- nand_cleanup(mtd_to_nand(mtd));
+ nand_disconnect(mtd_to_nand(mtd));
+
+ /* Apply write protection after draining outstanding operations. */
+ gpiod_set_value(priv->gpiod_nwp, 1);
+ mtd_put_master(mtd);
}
#ifdef CONFIG_OF
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 24/58] mtd: rawnand: xway: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (22 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 23/58] mtd: rawnand: ams-delta: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 25/58] mtd: rawnand: hisi504: " James Hilliard
` (33 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/xway_nand.c | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/xway_nand.c b/drivers/mtd/nand/raw/xway_nand.c
index 7fcefb24fc23..89c4cda08794 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>
@@ -171,10 +172,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, data);
+ if (err)
+ return err;
data->nandaddr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->nandaddr))
@@ -192,7 +195,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 +230,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_lifetime(&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 +255,8 @@ static void xway_nand_remove(struct platform_device *pdev)
ret = mtd_device_unregister(nand_to_mtd(chip));
WARN_ON(ret);
- nand_cleanup(chip);
+ nand_disconnect(chip);
+ mtd_put_master(nand_to_mtd(chip));
}
static const struct of_device_id xway_nand_match[] = {
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 25/58] mtd: rawnand: hisi504: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (23 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 24/58] mtd: rawnand: xway: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 26/58] mtd: rawnand: vf610: " James Hilliard
` (32 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/hisi504_nand.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/mtd/nand/raw/hisi504_nand.c b/drivers/mtd/nand/raw/hisi504_nand.c
index ccc21d21c3e0..a9e6ed2d4c4b 100644
--- a/drivers/mtd/nand/raw/hisi504_nand.c
+++ b/drivers/mtd/nand/raw/hisi504_nand.c
@@ -740,9 +740,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, host);
+ if (ret)
+ return ret;
host->dev = dev;
platform_set_drvdata(pdev, host);
@@ -788,10 +793,16 @@ static int hisi_nfc_probe(struct platform_device *pdev)
if (ret)
return ret;
+ ret = nand_set_lifetime(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 +817,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 26/58] mtd: rawnand: vf610: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (24 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 25/58] mtd: rawnand: hisi504: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 27/58] mtd: rawnand: technologic: " James Hilliard
` (31 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Cc: Stefan Agner <stefan@agner.ch>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/vf610_nfc.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index 57da9eab940e..16e84ade92db 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -813,15 +813,17 @@ 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, nfc);
+ if (err)
+ return err;
nfc->dev = &pdev->dev;
chip = &nfc->chip;
mtd = nand_to_mtd(chip);
- mtd->owner = THIS_MODULE;
mtd->parent_dev = nfc->dev;
mtd->name = DRV_NAME;
@@ -873,7 +875,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 +886,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_lifetime(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 +910,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 27/58] mtd: rawnand: technologic: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (25 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 26/58] mtd: rawnand: vf610: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 28/58] mtd: rawnand: loongson: " James Hilliard
` (30 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/technologic-nand-controller.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/technologic-nand-controller.c b/drivers/mtd/nand/raw/technologic-nand-controller.c
index a4de53f954e5..98cf91136d0b 100644
--- a/drivers/mtd/nand/raw/technologic-nand-controller.c
+++ b/drivers/mtd/nand/raw/technologic-nand-controller.c
@@ -139,11 +139,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, data);
+ if (err)
+ return err;
data->controller.ops = &ts72xx_nand_ops;
data->chip.controller = &data->controller;
@@ -175,10 +177,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_lifetime(&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 +200,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 28/58] mtd: rawnand: loongson: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (26 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 27/58] mtd: rawnand: technologic: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 29/58] mtd: rawnand: ma35d1: " James Hilliard
` (29 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c
index 741e19f351d1..7f8ea6819097 100644
--- a/drivers/mtd/nand/raw/loongson-nand-controller.c
+++ b/drivers/mtd/nand/raw/loongson-nand-controller.c
@@ -863,15 +863,20 @@ static int loongson_nand_chip_init(struct loongson_nand_host *host)
chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD;
chip->buf_align = 16;
mtd->parent_dev = dev;
- mtd->owner = THIS_MODULE;
ret = nand_scan(chip, 1);
if (ret)
return dev_err_probe(dev, ret, "failed to scan NAND chip\n");
- ret = mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_lifetime(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");
}
@@ -890,9 +895,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, host);
+ if (ret)
+ return ret;
host->reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(host->reg_base))
@@ -911,8 +919,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 +944,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 29/58] mtd: rawnand: ma35d1: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (27 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 28/58] mtd: rawnand: loongson: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 30/58] mtd: rawnand: pl35x: " James Hilliard
` (28 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 36 +++++++++++++++-------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
index 23db2f4fefbb..9d89263ac09c 100644
--- a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
+++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
@@ -866,8 +866,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 +875,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;
@@ -899,23 +900,33 @@ static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand,
nand_set_controller_data(chip, nand);
mtd = nand_to_mtd(chip);
- mtd->owner = THIS_MODULE;
+
mtd->parent_dev = dev;
mtd_set_ooblayout(mtd, &ma35_ooblayout_ops);
ret = nand_scan(chip, nsels);
if (ret)
- return ret;
+ goto free_chip;
+ ret = nand_set_lifetime(chip, container_of(chip, struct ma35_nand_chip, chip));
+ 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 +939,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));
}
}
@@ -953,11 +965,13 @@ 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, nand);
+ if (ret)
+ return ret;
INIT_LIST_HEAD(&nand->chips);
nand->controller.ops = &ma35_nfc_ops;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 30/58] mtd: rawnand: pl35x: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (28 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 29/58] mtd: rawnand: ma35d1: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 31/58] mtd: rawnand: mxc: " James Hilliard
` (27 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Cc: Michal Simek <michal.simek@amd.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/pl35x-nand-controller.c | 36 ++++++++++++++++++++--------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 537387014e2a..e5d86d72faf3 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -1057,22 +1057,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 +1091,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_lifetime(chip, plnand);
+ 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 +1131,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));
}
}
@@ -1153,12 +1167,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, nfc);
+ 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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 31/58] mtd: rawnand: mxc: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (29 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 30/58] mtd: rawnand: pl35x: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 32/58] mtd: rawnand: fsmc: " James Hilliard
` (26 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c
index f976cf3f7210..b68c2f13234f 100644
--- a/drivers/mtd/nand/raw/mxc_nand.c
+++ b/drivers/mtd/nand/raw/mxc_nand.c
@@ -1694,10 +1694,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, host);
+ 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 +1800,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_lifetime(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 +1831,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 32/58] mtd: rawnand: fsmc: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (30 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 31/58] mtd: rawnand: mxc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 33/58] mtd: rawnand: gpmi: " James Hilliard
` (25 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsmc_nand.c | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c
index 3cd25395d791..264a2570f40c 100644
--- a/drivers/mtd/nand/raw/fsmc_nand.c
+++ b/drivers/mtd/nand/raw/fsmc_nand.c
@@ -1037,9 +1037,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, host);
+ if (ret)
+ return ret;
nand = &host->nand;
@@ -1128,7 +1131,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 +1142,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_lifetime(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 +1184,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 33/58] mtd: rawnand: gpmi: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (31 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 32/58] mtd: rawnand: fsmc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 34/58] mtd: rawnand: omap2: " James Hilliard
` (24 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 17 +++++++++++++----
1 file changed, 13 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 4822485bd512..8fc78e679f0f 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_lifetime(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:
@@ -2787,9 +2792,12 @@ 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, this);
+ if (ret)
+ return ret;
this->devdata = of_device_get_match_data(&pdev->dev);
platform_set_drvdata(pdev, this);
@@ -2838,7 +2846,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 +2854,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 34/58] mtd: rawnand: omap2: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (32 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 33/58] mtd: rawnand: gpmi: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 35/58] mtd: rawnand: marvell: " James Hilliard
` (23 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Keep the shared GPMC lock for bound devices and separately retain each
device's allocation. Disconnected handles return before touching that
lock. Finish software BCH cleanup before releasing DMA and driver code.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/omap2.c | 25 ++++++++++++++++++-------
1 file changed, 18 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index aee7df4d768f..12a978f5f724 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 nand_storage storage;
struct nand_chip nand;
struct platform_device *pdev;
@@ -2218,10 +2219,12 @@ 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;
+ err = devm_nand_storage_init(dev, &info->storage, info);
+ if (err)
+ return err;
info->pdev = pdev;
@@ -2282,9 +2285,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_shared_lifetime(nand_chip, &info->storage);
if (err)
goto cleanup_nand;
+ err = mtd_device_register(mtd, NULL, 0);
+ if (err) {
+ nand_disconnect(nand_chip);
+ rawnand_sw_bch_cleanup(nand_chip);
+ mtd_put_master(mtd);
+ goto release_dma;
+ }
platform_set_drvdata(pdev, mtd);
@@ -2294,11 +2304,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 +2318,13 @@ 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);
+ WARN_ON(mtd_device_unregister(mtd));
+ nand_disconnect(nand_chip);
rawnand_sw_bch_cleanup(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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 35/58] mtd: rawnand: marvell: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (33 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 34/58] mtd: rawnand: omap2: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 36/58] mtd: rawnand: renesas: " James Hilliard
` (22 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/marvell_nand.c | 44 ++++++++++++++++++++++++-------------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c
index 56350209f7e0..7bd5537d6b62 100644
--- a/drivers/mtd/nand/raw/marvell_nand.c
+++ b/drivers/mtd/nand/raw/marvell_nand.c
@@ -2630,9 +2630,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 +2652,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 +2688,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 +2703,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 +2739,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_lifetime(chip, container_of(chip, struct marvell_nand_chip, chip));
+ 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 +2754,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 +2778,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));
}
}
@@ -2939,13 +2952,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, nfc);
+ 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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 36/58] mtd: rawnand: renesas: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (34 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 35/58] mtd: rawnand: marvell: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 37/58] mtd: rawnand: meson: " James Hilliard
` (21 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Cc: linux-renesas-soc@vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/renesas-nand-controller.c | 35 +++++++++++++++++---------
1 file changed, 23 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c
index 135cdb67a472..8a2a55007148 100644
--- a/drivers/mtd/nand/raw/renesas-nand-controller.c
+++ b/drivers/mtd/nand/raw/renesas-nand-controller.c
@@ -1217,8 +1217,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 +1228,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 +1258,29 @@ static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np)
mtd->parent_dev = 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_lifetime(chip, container_of(chip, struct rnand_chip, chip));
+ 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 +1289,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 +1304,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));
}
}
@@ -1322,12 +1331,14 @@ static int rnandc_probe(struct platform_device *pdev)
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);
+ 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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 37/58] mtd: rawnand: meson: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (35 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 36/58] mtd: rawnand: renesas: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 38/58] mtd: rawnand: cadence: " James Hilliard
` (20 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Free transfer buffers in the synchronous NAND detach callback.
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 | 47 +++++++++++++++++++++++++--------------
1 file changed, 30 insertions(+), 17 deletions(-)
diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c
index 2a554f62a25e..871dc5e22759 100644
--- a/drivers/mtd/nand/raw/meson_nand.c
+++ b/drivers/mtd/nand/raw/meson_nand.c
@@ -1398,8 +1398,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 +1409,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;
}
}
@@ -1427,21 +1427,23 @@ meson_nfc_nand_chip_init(struct device *dev,
nand->options |= NAND_USES_DMA;
mtd = nand_to_mtd(nand);
- mtd->owner = THIS_MODULE;
+
mtd->parent_dev = dev;
ret = of_property_read_u32(np, "nand-rb", &nand_rb_val);
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 +1451,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 +1459,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_lifetime(nand, to_meson_nand(nand));
+ 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 +1496,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);
}
}
@@ -1550,15 +1561,17 @@ static int meson_nfc_probe(struct platform_device *pdev)
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, nfc);
+ 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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 38/58] mtd: rawnand: cadence: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (36 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 37/58] mtd: rawnand: meson: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 39/58] mtd: rawnand: qcom: " James Hilliard
` (19 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cadence-nand-controller.c | 49 +++++++++++++++++---------
1 file changed, 33 insertions(+), 16 deletions(-)
diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c
index 05d0659e6527..4e365ffdf905 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;
/*
@@ -3007,9 +3008,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 +3023,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 +3060,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_lifetime(chip, cdns_chip);
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 +3086,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 +3133,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 +3188,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 +3207,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);
@@ -3275,11 +3289,14 @@ 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, dt);
+ if (ret)
+ return ret;
cdns_ctrl->caps1 = devdata;
cdns_ctrl->dev = &ofdev->dev;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 39/58] mtd: rawnand: qcom: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (37 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 38/58] mtd: rawnand: cadence: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 40/58] mtd: rawnand: cafe: " James Hilliard
` (18 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 37 ++++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index c2ccb999ad8d..3451687a2bf4 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 */
@@ -2022,15 +2021,17 @@ 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->parent_dev = dev;
/*
@@ -2053,7 +2054,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 +2062,15 @@ static int qcom_nand_host_init(struct qcom_nand_controller *nandc,
goto err;
}
+ ret = nand_set_lifetime(chip, to_qcom_nand_host(chip));
+ if (ret)
+ goto err;
return 0;
err:
nand_cleanup(chip);
+free_host:
+ kfree(host);
return ret;
}
@@ -2076,15 +2082,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 +2110,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);
}
}
@@ -2149,9 +2157,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, nandc);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, nandc);
nandc->dev = dev;
--
2.53.0
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 40/58] mtd: rawnand: cafe: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (38 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 39/58] mtd: rawnand: qcom: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 41/58] mtd: rawnand: brcmnand: " James Hilliard
` (17 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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 in the synchronous NAND detach callback while the PCI
resources are available.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cafe_nand.c | 25 ++++++++++++++++---------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c
index e936c9807bf8..be29e78d6408 100644
--- a/drivers/mtd/nand/raw/cafe_nand.c
+++ b/drivers/mtd/nand/raw/cafe_nand.c
@@ -683,6 +683,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);
+ if (err)
+ goto out_disable_device;
mtd = nand_to_mtd(&cafe->nand);
mtd->parent_dev = &pdev->dev;
@@ -693,7 +697,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 +789,15 @@ 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_lifetime(&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);
+ mtd_put_master(mtd);
+ goto out_irq;
+ }
goto out;
@@ -801,8 +811,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 +824,16 @@ 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);
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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 41/58] mtd: rawnand: brcmnand: retain open-handle state on removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (39 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 40/58] mtd: rawnand: cafe: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 42/58] mtd: rawnand: au1550nd: retain chip state across removal James Hilliard
` (16 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 63 +++++++++++++++++++++-----------
1 file changed, 42 insertions(+), 21 deletions(-)
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index 0378014da6d2..8b2ffe5d44af 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -3093,10 +3093,11 @@ 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->parent_dev = dev;
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
@@ -3123,13 +3124,24 @@ 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_lifetime(chip, container_of(chip, struct brcmnand_host, chip));
+ 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;
}
static void brcmnand_save_restore_cs_config(struct brcmnand_host *host,
@@ -3294,6 +3306,21 @@ 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);
+ }
+}
+
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
{
struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
@@ -3307,9 +3334,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, ctrl);
+ if (ret)
+ return ret;
dev_set_drvdata(dev, ctrl);
ctrl->dev = dev;
@@ -3324,7 +3354,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 +3518,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 +3529,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 +3540,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 +3556,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 +3582,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 +3592,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 42/58] mtd: rawnand: au1550nd: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (40 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 41/58] mtd: rawnand: brcmnand: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 43/58] mtd: rawnand: sharpsl: " James Hilliard
` (15 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/au1550nd.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/au1550nd.c b/drivers/mtd/nand/raw/au1550nd.c
index 01c2c8b191eb..83db1963c449 100644
--- a/drivers/mtd/nand/raw/au1550nd.c
+++ b/drivers/mtd/nand/raw/au1550nd.c
@@ -269,6 +269,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, ctx);
+ if (ret)
+ return ret;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -302,7 +305,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 +324,17 @@ static int au1550nd_probe(struct platform_device *pdev)
goto out3;
}
- mtd_device_register(mtd, pd->parts, pd->num_parts);
+ ret = nand_set_lifetime(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 +345,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 +357,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 43/58] mtd: rawnand: sharpsl: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (41 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 42/58] mtd: rawnand: au1550nd: retain chip state across removal James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 44/58] mtd: rawnand: davinci: " James Hilliard
` (14 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sharpsl.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/sharpsl.c b/drivers/mtd/nand/raw/sharpsl.c
index 5aea5afd480e..8376ac823c98 100644
--- a/drivers/mtd/nand/raw/sharpsl.c
+++ b/drivers/mtd/nand/raw/sharpsl.c
@@ -135,6 +135,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);
+ if (err)
+ return err;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
@@ -154,7 +157,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 +190,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_lifetime(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 +211,6 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
iounmap(sharpsl->io);
err_ioremap:
err_get_res:
- kfree(sharpsl);
return err;
}
@@ -221,12 +228,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 44/58] mtd: rawnand: davinci: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (42 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 43/58] mtd: rawnand: sharpsl: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 45/58] mtd: rawnand: sh_flctl: " James Hilliard
` (13 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard
Disconnect the chip before releasing the shared four-bit ECC engine.
Run NAND cleanup while the driver is bound, and retain chip and
controller allocations while MTD references remain.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/davinci_nand.c | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c
index 17c6fa73f4c3..40291db1fda5 100644
--- a/drivers/mtd/nand/raw/davinci_nand.c
+++ b/drivers/mtd/nand/raw/davinci_nand.c
@@ -919,9 +919,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, info);
+ if (ret)
+ return ret;
platform_set_drvdata(pdev, info);
@@ -986,7 +989,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 +997,18 @@ static int nand_davinci_probe(struct platform_device *pdev)
return ret;
}
+ ret = nand_set_lifetime(&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 +1028,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 45/58] mtd: rawnand: sh_flctl: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (43 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 44/58] mtd: rawnand: davinci: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 46/58] mtd: rawnand: lpc32xx_slc: " James Hilliard
` (12 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/sh_flctl.c | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c
index 36ba5b3da3a0..b230f5ef448b 100644
--- a/drivers/mtd/nand/raw/sh_flctl.c
+++ b/drivers/mtd/nand/raw/sh_flctl.c
@@ -1119,9 +1119,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);
+ if (ret)
+ return ret;
flctl->reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(flctl->reg))
@@ -1187,9 +1192,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_lifetime(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 +1218,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 46/58] mtd: rawnand: lpc32xx_slc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (44 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 45/58] mtd: rawnand: sh_flctl: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 47/58] mtd: rawnand: lpc32xx_mlc: " James Hilliard
` (11 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c
index f75ae484ce86..85472c3d4a40 100644
--- a/drivers/mtd/nand/raw/lpc32xx_slc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_slc.c
@@ -829,9 +829,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, host);
+ if (res)
+ return res;
host->io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &rc);
if (IS_ERR(host->io_base))
@@ -862,7 +867,7 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
mtd = nand_to_mtd(chip);
nand_set_controller_data(chip, host);
nand_set_flash_node(chip, pdev->dev.of_node);
- mtd->owner = THIS_MODULE;
+
mtd->parent_dev = &pdev->dev;
/* Get NAND clock */
@@ -915,10 +920,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_lifetime(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 +955,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 +964,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 47/58] mtd: rawnand: lpc32xx_mlc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (45 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 46/58] mtd: rawnand: lpc32xx_slc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 48/58] mtd: rawnand: ndfc: " James Hilliard
` (10 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c
index 408b2aba2fdd..633d09471760 100644
--- a/drivers/mtd/nand/raw/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c
@@ -687,9 +687,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, host);
+ if (res)
+ return res;
host->pdev = pdev;
@@ -794,10 +799,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_lifetime(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 +841,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 +852,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 48/58] mtd: rawnand: ndfc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (46 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 47/58] mtd: rawnand: lpc32xx_mlc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 49/58] mtd: rawnand: txx9ndfmc: " James Hilliard
` (9 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/ndfc.c | 32 ++++++++++++++++++++++----------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c
index ce10fc6a41aa..27ab5c034e70 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,7 +171,16 @@ static int ndfc_chip_init(struct ndfc_controller *ndfc,
if (ret)
goto err;
+ ret = nand_set_lifetime(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);
@@ -187,11 +194,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 +209,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);
+ 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 +254,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 49/58] mtd: rawnand: txx9ndfmc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (47 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 48/58] mtd: rawnand: ndfc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 50/58] mtd: rawnand: ingenic: " James Hilliard
` (8 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/txx9ndfmc.c | 39 ++++++++++++++++++++++++++++++---------
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/drivers/mtd/nand/raw/txx9ndfmc.c b/drivers/mtd/nand/raw/txx9ndfmc.c
index 6fd6e530c38b..bd0cc30ab820 100644
--- a/drivers/mtd/nand/raw/txx9ndfmc.c
+++ b/drivers/mtd/nand/raw/txx9ndfmc.c
@@ -276,17 +276,28 @@ 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 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, drvdata);
+ if (ret)
+ return ret;
drvdata->base = devm_platform_ioremap_resource(dev, 0);
if (IS_ERR(drvdata->base))
return PTR_ERR(drvdata->base);
@@ -306,7 +317,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 +371,20 @@ static int txx9ndfmc_probe(struct platform_device *dev)
}
mtd->name = txx9_priv->mtdname;
- mtd_device_register(mtd, NULL, 0);
+ ret = nand_set_lifetime(chip, txx9_priv);
+ if (ret) {
+ nand_cleanup(chip);
+ txx9ndfmc_chip_release(chip);
+ continue;
+ }
+ kfree(txx9_priv->mtdname);
+ txx9_priv->mtdname = NULL;
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_disconnect(chip);
+ mtd_put_master(mtd);
+ continue;
+ }
drvdata->mtds[i] = mtd;
}
@@ -376,18 +399,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 50/58] mtd: rawnand: ingenic: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (48 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 49/58] mtd: rawnand: txx9ndfmc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 51/58] mtd: rawnand: mpc5121: " James Hilliard
` (7 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 39 +++++++++++++++++--------
1 file changed, 27 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 bdda06ae0d72..2818ffe76796 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
@@ -368,7 +368,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 +377,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 +385,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->parent_dev = dev;
chip->options = NAND_NO_SUBPAGE_WRITE;
@@ -404,17 +406,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_lifetime(chip, to_ingenic_nand(nand_to_mtd(chip)));
+ 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 +441,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));
}
}
@@ -474,9 +487,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, nfc);
+ if (ret)
+ return ret;
nfc->soc_info = device_get_match_data(dev);
if (!nfc->soc_info)
@@ -493,7 +509,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 +526,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 51/58] mtd: rawnand: mpc5121: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (49 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 50/58] mtd: rawnand: ingenic: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 52/58] mtd: rawnand: bcm47xxnflash: " James Hilliard
` (6 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
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 | 24 +++++++++++++++++-------
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c
index 0aeeb6327472..42e167e1db7e 100644
--- a/drivers/mtd/nand/raw/mpc5121_nfc.c
+++ b/drivers/mtd/nand/raw/mpc5121_nfc.c
@@ -636,6 +636,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, prv);
+ if (retval)
+ return retval;
+
regs = devm_platform_ioremap_resource(op, 0);
if (IS_ERR(regs))
return PTR_ERR(regs);
@@ -644,14 +651,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 +784,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_lifetime(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 +817,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 52/58] mtd: rawnand: bcm47xxnflash: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (50 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 51/58] mtd: rawnand: mpc5121: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 53/58] mtd: rawnand: fsl_elbc: " James Hilliard
` (5 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/bcm47xxnflash/main.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/main.c b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
index 4cccf00f67f7..fac96ec596bc 100644
--- a/drivers/mtd/nand/raw/bcm47xxnflash/main.c
+++ b/drivers/mtd/nand/raw/bcm47xxnflash/main.c
@@ -26,9 +26,13 @@ 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, b47n);
+ if (err)
+ return err;
nand_set_controller_data(&b47n->nand_chip, b47n);
mtd = nand_to_mtd(&b47n->nand_chip);
@@ -48,9 +52,16 @@ static int bcm47xxnflash_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, b47n);
+ err = nand_set_lifetime(&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 +76,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 53/58] mtd: rawnand: fsl_elbc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (51 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 52/58] mtd: rawnand: bcm47xxnflash: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 54/58] mtd: rawnand: fsl_ifc: " James Hilliard
` (4 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup before releasing the hardware. The NAND core releases
chip storage and the shared controller reference after the final MTD put.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_elbc_nand.c | 54 +++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 23 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c
index 6216ffea4324..f1034b70f717 100644
--- a/drivers/mtd/nand/raw/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c
@@ -837,23 +837,22 @@ 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 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;
- struct mtd_info *mtd = nand_to_mtd(&priv->chip);
-
- kfree(mtd->name);
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;
+ nand_storage_put(&elbc_fcm_ctrl->controller.storage);
}
-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 +866,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 +904,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++;
+ nand_storage_init(&elbc_fcm_ctrl->controller.storage, elbc_fcm_ctrl);
nand_controller_init(&elbc_fcm_ctrl->controller);
fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl;
} else {
elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand;
+ nand_storage_get(&elbc_fcm_ctrl->controller.storage);
}
+ 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 +949,21 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
if (ret)
goto err;
+ name = mtd->name;
+ ret = nand_set_lifetime(&priv->chip, priv);
+ 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 +973,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);
+ kfree(priv);
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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 54/58] mtd: rawnand: fsl_ifc: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (52 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 53/58] mtd: rawnand: fsl_elbc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 55/58] mtd: rawnand: r852: " James Hilliard
` (3 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/fsl_ifc_nand.c | 46 +++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 12 deletions(-)
diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c
index 20ae012adbaa..8b48c94ea8c5 100644
--- a/drivers/mtd/nand/raw/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c
@@ -40,6 +40,7 @@ struct fsl_ifc_mtd {
/* overview of the fsl ifc controller */
struct fsl_ifc_nand_ctrl {
struct nand_controller controller;
+ unsigned int users;
struct fsl_ifc_mtd *chips[FSL_IFC_BANK_COUNT];
void __iomem *addr; /* Address of assigned IFC buffer */
@@ -961,11 +962,20 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
return 0;
}
-static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv)
-{
- ifc_nand_ctrl->chips[priv->bank] = NULL;
+static DEFINE_MUTEX(fsl_ifc_nand_mutex);
- return 0;
+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;
+ }
+ nand_storage_put(&ctrl->controller.storage);
}
static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank,
@@ -981,8 +991,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 +1024,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);
+ nand_storage_init(&ifc_nand_ctrl->controller.storage, ifc_nand_ctrl);
} else {
ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand;
+ nand_storage_get(&ifc_nand_ctrl->controller.storage);
}
+ 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 +1084,19 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
ret = nand_scan(&priv->chip, 1);
if (ret)
goto err;
+ ret = nand_set_lifetime(&priv->chip, priv);
+ 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 +1107,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
nand_cleanup(&priv->chip);
err:
fsl_ifc_chip_remove(priv);
+ kfree(priv);
return ret;
}
@@ -1099,9 +1120,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 55/58] mtd: rawnand: r852: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (53 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 54/58] mtd: rawnand: fsl_ifc: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 56/58] mtd: rawnand: nandsim: " James Hilliard
` (2 subsequent siblings)
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Cc: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/r852.c | 103 +++++++++++++++++++++++----------------
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, 65 insertions(+), 56 deletions(-)
diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c
index 566662d45c2c..3c3161bc4b30 100644
--- a/drivers/mtd/nand/raw/r852.c
+++ b/drivers/mtd/nand/raw/r852.c
@@ -625,16 +625,29 @@ 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
- */
+/* 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->parent_dev = &dev->pci_dev->dev;
if (dev->readonly)
@@ -642,8 +655,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_lifetime(chip, chip);
+ 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 +675,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 +696,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 +766,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 +784,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;
}
@@ -844,7 +878,6 @@ static const struct nand_controller_ops r852_ops = {
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 +899,27 @@ 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, dev);
+ 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 +979,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 +991,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 +1015,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 56/58] mtd: rawnand: nandsim: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (54 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 55/58] mtd: rawnand: r852: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 57/58] mtd: rawnand: cs553x: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 58/58] mtd: rawnand: diskonchip: " James Hilliard
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard
A failed registration or subsequent debugfs initialization can unwind
after MTD handles have been acquired. Disconnect before freeing the
simulated media, and retain chip metadata until the final MTD put. Use
the shared lifetime cleanup for both initialization rollback and module
removal.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/nandsim.c | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c
index 6716eafb4b00..dd000c766ed3 100644
--- a/drivers/mtd/nand/raw/nandsim.c
+++ b/drivers/mtd/nand/raw/nandsim.c
@@ -2263,6 +2263,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);
@@ -2317,8 +2318,6 @@ static int __init ns_init_module(void)
chip->options |= NAND_BUSWIDTH_16;
}
- nsmtd->owner = THIS_MODULE;
-
ret = ns_parse_weakblocks();
if (ret)
goto free_ns_struct;
@@ -2377,6 +2376,10 @@ static int __init ns_init_module(void)
ret = ns_parse_badblocks(ns, nsmtd);
if (ret)
goto free_ns_object;
+ ret = nand_set_lifetime(chip, nand_get_controller_data(chip));
+ if (ret)
+ goto free_ns_object;
+ retained = true;
/* Register NAND partitions */
ret = mtd_device_register(nsmtd, &ns->partitions[0], ns->nbparts);
@@ -2392,11 +2395,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 +2421,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 +2440,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 +2459,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 57/58] mtd: rawnand: cs553x: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (55 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 56/58] mtd: rawnand: nandsim: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
2026-11-12 1:28 ` [PATCH v3 58/58] mtd: rawnand: diskonchip: " James Hilliard
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard
Keep chip metadata alive while MTD references remain after a partial
registration is unwound. 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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/cs553x_nand.c | 44 +++++++++++++++++++++++++-------------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c
index 0b872b1e3b04..4a43f5e542ca 100644
--- a/drivers/mtd/nand/raw/cs553x_nand.c
+++ b/drivers/mtd/nand/raw/cs553x_nand.c
@@ -263,6 +263,7 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
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);
@@ -285,9 +286,6 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
this->controller = &controller->base;
new_mtd = nand_to_mtd(this);
- /* Link the private data with the MTD structure */
- new_mtd->owner = THIS_MODULE;
-
/* map physical address */
controller->mmio = ioremap(adr, 4096);
if (!controller->mmio) {
@@ -309,6 +307,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_lifetime(this, container_of(this, struct cs553x_nand_controller, chip));
+ if (err) {
+ nand_cleanup(this);
+ goto out_free;
+ }
+ kfree(name);
controllers[cs] = controller;
goto out;
@@ -344,6 +349,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 +380,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 +406,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* [PATCH v3 58/58] mtd: rawnand: diskonchip: retain chip state across removal
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
` (56 preceding siblings ...)
2026-11-12 1:28 ` [PATCH v3 57/58] mtd: rawnand: cs553x: " James Hilliard
@ 2026-11-12 1:28 ` James Hilliard
57 siblings, 0 replies; 62+ messages in thread
From: James Hilliard @ 2026-11-12 1:28 UTC (permalink / raw)
To: Miquel Raynal, Richard Weinberger, Vignesh Raghavendra
Cc: linux-mtd, linux-kernel, Greg Kroah-Hartman, James Hilliard
MTD references may outlive a partially published registration failure.
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.
Run NAND cleanup during disconnection; the final core-owned put only
releases retained allocation and layout data.
Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
---
drivers/mtd/nand/raw/diskonchip.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/drivers/mtd/nand/raw/diskonchip.c b/drivers/mtd/nand/raw/diskonchip.c
index 540b6baf8bb1..2c6d0c0fdb2d 100644
--- a/drivers/mtd/nand/raw/diskonchip.c
+++ b/drivers/mtd/nand/raw/diskonchip.c
@@ -1466,7 +1466,6 @@ static int __init doc_probe(unsigned long physadr)
nand->bbt_td = (struct nand_bbt_descr *) (doc + 1);
nand->bbt_md = nand->bbt_td + 1;
- mtd->owner = THIS_MODULE;
mtd_set_ooblayout(mtd, &doc200x_ooblayout_ops);
nand->controller = &doc->base;
@@ -1494,13 +1493,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_lifetime(nand, nand);
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 +1542,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
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply related [flat|nested] 62+ messages in thread
* Re: [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns
2026-11-12 1:27 ` [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns James Hilliard
@ 2026-11-12 2:00 ` sashiko-bot
0 siblings, 0 replies; 62+ messages in thread
From: sashiko-bot @ 2026-11-12 2:00 UTC (permalink / raw)
To: James Hilliard, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra
Cc: linux-mtd, Greg Kroah-Hartman, Srinivas Kandagatla, Tze Yee Ng
Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [Medium] nvmem: nvmem_populate_sysfs_cells() skips layout cells and
tags provider cells
- [Medium] nvmem: leaked devres and stale cells_group in
nvmem_populate_sysfs_cells()
- [Medium] nvmem: unpaired NVMEM_REMOVE notifier event on
nvmem_register() failure
- [Medium] nvmem: sysfs bin_attribute UAF during device_del() in
nvmem_unregister()
- [Low] nvmem: unreachable disconnected check in nvmem_device_match()
--
commit 7e668831f84b8e65137e3150ab3a241d5d20c765
Author: James Hilliard <james.hilliard1@gmail.com>
nvmem: disconnect provider callbacks before unregister returns
This commit disconnects NVMEM provider and layout callbacks using SRCU
when unregistering a provider or layout, and adds reference counting to
struct nvmem_cell_entry so consumers can safely outlive provider unbind.
> diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
> index 0556d140170a4..8571d821591c9 100644
> --- a/drivers/nvmem/core.c
> +++ b/drivers/nvmem/core.c
[ ... ]
> @@ -511,9 +516,10 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
> i++;
> }
>
> - group.bin_attrs = pattrs;
> + nvmem->cells_group.name = "cells";
> + nvmem->cells_group.bin_attrs = pattrs;
>
> - ret = device_add_group(&nvmem->dev, &group);
> + ret = device_add_group(&nvmem->dev, &nvmem->cells_group);
> if (ret)
> return ret;
[Severity: Medium]
Does an error in nvmem_populate_sysfs_cells() during layout registration
leak the devres allocations on nvmem->dev and leave a stale
nvmem->cells_group.bin_attrs pointer?
pattrs, attrs, and attrs[i].attr.name are allocated with devm_kcalloc()
and devm_kasprintf() on the parent &nvmem->dev, not &layout->dev. If one
of those allocations or device_add_group() fails (for example, with
-EEXIST on a duplicate cell attribute name), nvmem->sysfs_cells_populated
remains false. When nvmem_layout_register() then unwinds through
nvmem_layout_unregister(), the devm_kfree() cleanup is skipped because
nvmem->sysfs_cells_populated is false, leaking those allocations across
layout probe retries and leaving nvmem->cells_group.bin_attrs pointing to
attributes whose private pointers reference freed nvmem_cell_entry
objects.
[ ... ]
> @@ -663,6 +682,8 @@ int nvmem_add_one_cell(struct nvmem_device *nvmem,
> return rval;
> }
>
> + kref_init(&cell->refcnt);
> + cell->layout = nvmem->layout;
> nvmem_cell_entry_add(cell);
>
> return 0;
[ ... ]
> @@ -851,29 +877,81 @@ int nvmem_layout_register(struct nvmem_layout *layout)
> if (!layout->add_cells)
> return -EINVAL;
>
> - /* Populate the cells */
> - ret = layout->add_cells(layout);
> + scoped_guard(srcu, &layout->nvmem->srcu) {
> + if (READ_ONCE(layout->nvmem->disconnected))
> + return -ENODEV;
> + ret = layout->add_cells(layout);
> + }
> if (ret)
> - return ret;
> + goto err_unregister;
>
> #ifdef CONFIG_NVMEM_SYSFS
> ret = nvmem_populate_sysfs_cells(layout->nvmem);
> - if (ret) {
> - nvmem_device_remove_all_cells(layout->nvmem);
> - return ret;
> - }
> + if (ret)
> + goto err_unregister;
> #endif
>
> return 0;
> +
> +err_unregister:
> + nvmem_layout_unregister(layout);
> + return ret;
> }
> EXPORT_SYMBOL_GPL(nvmem_layout_register);
>
> void nvmem_layout_unregister(struct nvmem_layout *layout)
> {
> - /* Keep the API even with an empty stub in case we need it later */
> + struct nvmem_device *nvmem = layout->nvmem;
> + struct nvmem_cell_entry *cell, *next;
> + struct attribute_group group = {};
> + LIST_HEAD(cells);
> +
> + scoped_guard(mutex, &nvmem_mutex) {
> + if (nvmem->sysfs_cells_populated)
> + group = nvmem->cells_group;
> + list_for_each_entry_safe(cell, next, &nvmem->cells, node) {
> + if (cell->layout != layout)
> + continue;
> + WRITE_ONCE(cell->disconnected, true);
> + list_move_tail(&cell->node, &cells);
> + }
> + }
> +
> + if (group.bin_attrs) {
> + device_remove_group(&nvmem->dev, &group);
> + scoped_guard(mutex, &nvmem_mutex) {
> + nvmem->sysfs_cells_populated = false;
> + memset(&nvmem->cells_group, 0, sizeof(nvmem->cells_group));
> + }
> + for (int i = 0; group.bin_attrs[i]; i++)
> + devm_kfree(&nvmem->dev, (void *)group.bin_attrs[i]->attr.name);
> + devm_kfree(&nvmem->dev, (void *)group.bin_attrs[0]);
> + devm_kfree(&nvmem->dev, group.bin_attrs);
> + }
> +
> + /* Old cell handles retain data, never a departing layout's callbacks. */
> + synchronize_srcu(&nvmem->srcu);
> + list_for_each_entry_safe(cell, next, &cells, node) {
> + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
> + list_del(&cell->node);
> + nvmem_cell_entry_put(cell);
> + }
> +#ifdef CONFIG_NVMEM_SYSFS
> + if (!READ_ONCE(nvmem->disconnected) && nvmem_populate_sysfs_cells(nvmem))
> + dev_warn(&nvmem->dev, "Failed to restore provider cell attributes\n");
> +#endif
> }
> EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
[Severity: Medium]
Can nvmem_populate_sysfs_cells() skip layout cell sysfs attributes when
provider cell attributes are already populated, and can nvmem_add_one_cell()
misattribute provider cells to nvmem->layout?
If an nvmem_device has provider cells (or if nvmem_layout_unregister()
restored provider cell attributes on unbind), nvmem_populate_sysfs_cells()
has already set nvmem->sysfs_cells_populated = true. When a layout driver
later probes (or rebinds) and nvmem_layout_register() calls
nvmem_populate_sysfs_cells(), it sees nvmem->sysfs_cells_populated == true
and returns 0 without rebuilding nvmem->cells_group, omitting the layout
cells from sysfs. A similar race exists during nvmem_register() because
nvmem_populate_layout() publishes layout->dev on nvmem_layout_bus_type
before nvmem_register() calls nvmem_populate_sysfs_cells().
In addition, nvmem->layout is set in nvmem_layout_create_device() for the
lifetime of the layout device, so setting cell->layout = nvmem->layout in
nvmem_add_one_cell() tags any cells added by a provider after
nvmem_register() (such as in mtk_mfg_init_nvmem_provider() or
u_boot_env_probe()) as layout cells and removes them on layout unbind.
[ ... ]
> @@ -1010,7 +1086,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
>
> rval = nvmem_populate_layout(nvmem);
> if (rval)
> - goto err_remove_dev;
> + goto err_unregister;
>
> /* If the device has WP GPIO, default to read-only */
> if (nvmem->wp_gpio)
> @@ -1019,23 +1095,24 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
> #ifdef CONFIG_NVMEM_SYSFS
> rval = nvmem_populate_sysfs_cells(nvmem);
> if (rval)
> - goto err_destroy_layout;
> + goto err_unregister;
> #endif
>
> blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem);
>
> return nvmem;
>
> -#ifdef CONFIG_NVMEM_SYSFS
> -err_destroy_layout:
> - nvmem_destroy_layout(nvmem);
> -#endif
> -err_remove_dev:
> - device_del(&nvmem->dev);
> +err_unregister:
> + nvmem_unregister(nvmem);
> + return ERR_PTR(rval);
[Severity: Medium]
Does jumping to err_unregister on failure in nvmem_populate_layout() or
nvmem_populate_sysfs_cells() emit an unpaired NVMEM_REMOVE notification?
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem) is only
called after both functions succeed, but nvmem_unregister()
unconditionally sends NVMEM_REMOVE.
[ ... ]
> @@ -1048,24 +1125,29 @@ static void nvmem_device_release(struct kref *kref)
>
> nvmem = container_of(kref, struct nvmem_device, refcnt);
>
> - blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
> -
> - nvmem_sysfs_remove_compat(nvmem);
> -
> - nvmem_device_remove_all_cells(nvmem);
> - nvmem_destroy_layout(nvmem);
> - device_unregister(&nvmem->dev);
> + put_device(&nvmem->dev);
> }
>
> /**
> * nvmem_unregister() - Unregister previously registered nvmem device
> *
> * @nvmem: Pointer to previously registered nvmem device.
> + *
> + * Stop and drain provider callbacks before returning. Existing consumers keep
> + * their core-owned metadata, but further access returns -ENODEV.
> */
> void nvmem_unregister(struct nvmem_device *nvmem)
> {
> - if (nvmem)
> - kref_put(&nvmem->refcnt, nvmem_device_release);
> + if (!nvmem)
> + return;
> +
> + nvmem_disconnect(nvmem);
> + nvmem_destroy_layout(nvmem);
> + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
> + nvmem_sysfs_remove_compat(nvmem);
> + device_del(&nvmem->dev);
> + nvmem_device_remove_all_cells(nvmem);
> + kref_put(&nvmem->refcnt, nvmem_device_release);
> }
[Severity: Medium]
Can device_del(&nvmem->dev) in nvmem_unregister() cause a use-after-free
on the devres-allocated bin_attribute array if a sysfs read races with
unregistration?
nvmem_populate_sysfs_cells() allocates pattrs, attrs, and
attrs[i].attr.name with devm_kcalloc() and devm_kasprintf() on &nvmem->dev,
and registers them via non-devres device_add_group(&nvmem->dev,
&nvmem->cells_group). When nvmem_unregister() runs with provider cell
attributes populated, it calls device_del(&nvmem->dev) without first
calling device_remove_group(&nvmem->dev, &nvmem->cells_group):
CPU0 CPU1
nvmem_unregister()
device_del(&nvmem->dev)
devres_release_all(&nvmem->dev)
/* frees attrs */
sysfs_kf_bin_read()
battr = of->kn->priv;
battr->read(...) <-- use-after-free
kobject_del(&nvmem->dev.kobj)
In device_del(), device_remove_attrs() only removes dev->groups, so
nvmem->cells_group remains active in sysfs until kobject_del() runs after
devres_release_all().
[ ... ]
> @@ -1103,26 +1185,46 @@ struct nvmem_device *devm_nvmem_register(struct device *dev,
> }
> EXPORT_SYMBOL_GPL(devm_nvmem_register);
>
> +struct nvmem_match_data {
> + void *data;
> + int (*match)(struct device *dev, const void *data);
> +};
> +
> +static int nvmem_match_device(struct device *dev, const void *data)
> +{
> + const struct nvmem_match_data *match = data;
> + struct nvmem_device *nvmem = to_nvmem_device(dev);
> +
> + guard(srcu)(&nvmem->srcu);
> + if (READ_ONCE(nvmem->disconnected))
> + return 0;
> + return match->match(dev, match->data);
> +}
> +
> static struct nvmem_device *nvmem_device_match(void *data,
> int (*match)(struct device *dev, const void *data))
> {
> + struct nvmem_match_data match_data = { .data = data, .match = match };
> struct nvmem_device *nvmem = NULL;
> struct device *dev;
>
> - scoped_guard(mutex, &nvmem_mutex) {
> - dev = bus_find_device(&nvmem_bus_type, NULL, data, match);
> - if (dev)
> - nvmem = to_nvmem_device(dev);
> - }
> + guard(mutex)(&nvmem_mutex);
> + dev = bus_find_device(&nvmem_bus_type, NULL, &match_data, nvmem_match_device);
> + if (dev)
> + nvmem = to_nvmem_device(dev);
> if (!nvmem)
> return ERR_PTR(-EPROBE_DEFER);
>
> + if (READ_ONCE(nvmem->disconnected)) {
> + put_device(dev);
> + return ERR_PTR(-ENODEV);
> + }
[Severity: Low]
Is the second READ_ONCE(nvmem->disconnected) check in nvmem_device_match()
reachable?
nvmem_disconnect() sets nvmem->disconnected under nvmem_mutex, and
nvmem_device_match() holds nvmem_mutex across bus_find_device(), where
nvmem_match_device() already rejects disconnected devices.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-submit-rawnand-retained-handle-lifetime-v1-v3-0-ce537999741d@gmail.com?part=1
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH v3 03/58] mtd: separate class devices from retained backing objects
2026-11-12 1:27 ` [PATCH v3 03/58] mtd: separate class devices from retained backing objects James Hilliard
@ 2026-11-12 2:48 ` sashiko-bot
0 siblings, 0 replies; 62+ messages in thread
From: sashiko-bot @ 2026-11-12 2:48 UTC (permalink / raw)
To: James Hilliard, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra
Cc: linux-mtd, Greg Kroah-Hartman, Srinivas Kandagatla, Tze Yee Ng
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] mtd: vmlinux link failure when CONFIG_MTD=m due to obj-y
subdirs under CONFIG_MTD_DEVICE=y
- [Medium] mtd: mtd_otp_nvmem_register() falls back to parent driver
owner when mtd->owner is NULL
- [Medium] mtd: NULL mtd->dev dereference in __mtd_del_partition() on
race
--
commit 9d436aa88bcc3bc57c2e1b8cfecc83b868e1eda7
Author: James Hilliard <james.hilliard1@gmail.com>
mtd: separate class devices from retained backing objects
This commit allocates MTD class devices separately from struct mtd_info,
places the class and device release handling into a built-in component, and
adds reference-counted backing lifetime management via mtd_set_release() and
mtd_put_master().
> diff --git a/drivers/Makefile b/drivers/Makefile
> index 0841ea851847e..86adb803c79a4 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -84,7 +84,7 @@ obj-y += scsi/
> obj-y += nvme/
> obj-$(CONFIG_ATA) += ata/
> obj-$(CONFIG_TARGET_CORE) += target/
> -obj-$(CONFIG_MTD) += mtd/
> +obj-$(CONFIG_MTD_DEVICE) += mtd/
> obj-$(CONFIG_SPI) += spi/
> obj-$(CONFIG_SPMI) += spmi/
> obj-$(CONFIG_HSI) += hsi/
[ ... ]
> diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
> index 0421c6208de77..d13a065874bad 100644
> --- a/drivers/mtd/Kconfig
> +++ b/drivers/mtd/Kconfig
> @@ -1,5 +1,9 @@
> +config MTD_DEVICE
> + bool
> +
> menuconfig MTD
> tristate "Memory Technology Device (MTD) support"
> + select MTD_DEVICE
> imply NVMEM
> help
> Memory Technology Devices are flash, RAM and similar chips, often
> diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
> index 7b6dd53e81504..8bb19a6da358a 100644
> --- a/drivers/mtd/Makefile
> +++ b/drivers/mtd/Makefile
> @@ -4,6 +4,7 @@
> #
>
> # Core functionality.
> +obj-$(CONFIG_MTD_DEVICE) += mtddevice.o
> obj-$(CONFIG_MTD) += mtd.o
> mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
> mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o
[Severity: High]
Does entering drivers/mtd/ via bool CONFIG_MTD_DEVICE break linking vmlinux
when CONFIG_MTD=m?
With CONFIG_MTD_DEVICE=y, Kbuild descends into drivers/mtd/ for built-in
objects and follows the unconditional obj-y subdirectories in
drivers/mtd/Makefile:
drivers/mtd/Makefile:
obj-y += parsers/
...
obj-y += chips/ lpddr/ maps/ devices/ nand/ tests/
Any bool Kconfig option inside those subdirectories (such as
CONFIG_MTD_BCM63XX_PARTS=y under allmodconfig or CONFIG_MTD_NAND_XWAY=y) is
then compiled into vmlinux while referencing symbols exported by modular
mtd.ko or nand.ko, failing the vmlinux link. Gating those subdirectory
assignments on obj-$(CONFIG_MTD) instead of obj-y would avoid built-in
traversal when CONFIG_MTD=m.
[ ... ]
> diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
> index 620326279957f..5793c865f3a20 100644
> --- a/drivers/mtd/mtdcore.c
> +++ b/drivers/mtd/mtdcore.c
[ ... ]
> @@ -901,13 +949,13 @@ 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)
> - mtd->owner = mtd->dev.parent->driver->owner;
> + if (mtd->parent_dev) {
> + if (!mtd->master.release && !mtd->owner && mtd->parent_dev->driver)
> + mtd->owner = mtd->parent_dev->driver->owner;
> if (!mtd->name)
> - mtd->name = dev_name(mtd->dev.parent);
> + mtd->name = dev_name(mtd->parent_dev);
> } else {
> pr_debug("mtd device won't show a device symlink in sysfs\n");
> }
> @@ -960,13 +1008,13 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
> struct device_node *np;
>
> /* DT binding is optional */
> - np = of_get_compatible_child(mtd->dev.of_node, compatible);
> + np = of_get_compatible_child(mtd_get_of_node(mtd), compatible);
>
> /* OTP nvmem will be registered on the physical device */
> - config.dev = mtd->dev.parent;
> + config.dev = mtd->parent_dev;
> 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;
[Severity: Medium]
When a master uses mtd_set_release() and its operations core is built-in,
mtd_set_dev_defaults() leaves mtd->owner as NULL, so does passing
config.owner = mtd->owner with config.dev = mtd->parent_dev cause
nvmem_register() to fall back to the controller driver's module?
drivers/nvmem/core.c:nvmem_register() {
...
nvmem->owner = config->owner;
if (!nvmem->owner && config->dev->driver)
nvmem->owner = config->dev->driver->owner;
...
}
Because config.dev is mtd->parent_dev, nvmem_register() sets nvmem->owner to
mtd->parent_dev->driver->owner when mtd->owner is NULL, pinning the
controller driver module whenever an OTP NVMEM device or cell is held.
[ ... ]
> diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
> index c316cfe92c7e4..eb7704a5d7141 100644
> --- a/drivers/mtd/mtdpart.c
> +++ b/drivers/mtd/mtdpart.c
[ ... ]
> @@ -329,7 +329,7 @@ static int __mtd_del_partition(struct mtd_info *mtd)
> return err;
> }
>
> - sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
> + sysfs_remove_files(&mtd->dev->kobj, mtd_partition_attrs);
>
> list_del_init(&mtd->part.node);
> err = del_mtd_device(mtd);
[Severity: Medium]
Can __mtd_del_partition() dereference a NULL mtd->dev if BLKPG_DEL_PARTITION
races with add_mtd_partitions()?
Previously mtd->dev was an embedded struct device, so &mtd->dev.kobj was
valid even before registration. Now child->dev is NULL until
add_mtd_device() allocates it, but add_mtd_partitions() links child onto
parent->partitions and drops master->master.partitions_lock before calling
add_mtd_device():
CPU0 CPU1
add_mtd_partitions()
mutex_lock(&partitions_lock)
list_add_tail(&child->part.node,
&parent->partitions)
mutex_unlock(&partitions_lock)
mtd_del_partition()
mutex_lock(&partitions_lock)
__mtd_del_partition(child)
sysfs_remove_files(
&child->dev->kobj, ...)
<-- NULL dereference
add_mtd_device(child)
When CONFIG_MTD_PARTITIONED_MASTER=y (or when parsing subpartitions of an
already-registered partition), the parent is already accessible to
BLKPG_DEL_PARTITION ioctls while child->dev is still NULL.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-submit-rawnand-retained-handle-lifetime-v1-v3-0-ce537999741d@gmail.com?part=3
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 62+ messages in thread
* Re: [PATCH v3 04/58] mtd: retain virtual concatenations across component removal
2026-11-12 1:27 ` [PATCH v3 04/58] mtd: retain virtual concatenations across component removal James Hilliard
@ 2026-11-12 3:09 ` sashiko-bot
0 siblings, 0 replies; 62+ messages in thread
From: sashiko-bot @ 2026-11-12 3:09 UTC (permalink / raw)
To: James Hilliard, Miquel Raynal, Richard Weinberger,
Vignesh Raghavendra
Cc: linux-mtd, Greg Kroah-Hartman, Yuqiang Sun, Amit Kumar Mahapatra,
Luca Ceresoli, Bernhard Frauendienst, Gustavo A. R. Silva,
Kees Cook
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Medium] mtd: mtdconcat: mtd_concat_create() checks subdev ooblayout
instead of master
--
commit 34bd63d3c8b343ca3ff6e172ada709306e4291bd
Author: James Hilliard <james.hilliard1@gmail.com>
mtd: retain virtual concatenations across component removal
This commit reworks virtual MTD concatenation lifetime management, using
reference counting for staged partition components and SRCU to synchronize
forwarding operations during disconnect and teardown.
Link: https://lore.kernel.org/r/20261103143953.69642-1-izaiahsun@gmail.com/
Link: https://lore.kernel.org/r/sashiko-outbox-195551@kernel.org/
> diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c
> index c97167d51fe2d..cce3e2fcafc84 100644
> --- a/drivers/mtd/mtdconcat.c
> +++ b/drivers/mtd/mtdconcat.c
[ ... ]
> @@ -738,7 +822,8 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
>
> }
>
> - mtd_set_ooblayout(&concat->mtd, subdev[0]->ooblayout);
> + if (subdev[0]->ooblayout)
> + mtd_set_ooblayout(&concat->mtd, &concat_ooblayout_ops);
[Severity: Medium]
Should this check mtd_get_master(subdev[0])->ooblayout instead of
subdev[0]->ooblayout?
allocate_partition() does not copy ooblayout onto child partition mtd_info
structs; mtd_ooblayout_ecc() and mtd_ooblayout_free() look it up on
mtd_get_master(mtd). When mtd_concat_create() is called with partition
subdevices (as mtd_virt_concat_register() always does), subdev[0]->ooblayout
is NULL, so concat_ooblayout_ops is never installed and OOB layout queries on
the concatenated device fail with -ENOTSUPP.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261111-submit-rawnand-retained-handle-lifetime-v1-v3-0-ce537999741d@gmail.com?part=4
______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/
^ permalink raw reply [flat|nested] 62+ messages in thread
end of thread, other threads:[~2026-11-12 3:09 UTC | newest]
Thread overview: 62+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-12 1:27 [PATCH v3 00/58] mtd: rawnand: retain chip and controller state across removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 01/58] nvmem: disconnect provider callbacks before unregister returns James Hilliard
2026-11-12 2:00 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 02/58] mtd: acquire an MTD handle from a referenced class device James Hilliard
2026-11-12 1:27 ` [PATCH v3 03/58] mtd: separate class devices from retained backing objects James Hilliard
2026-11-12 2:48 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 04/58] mtd: retain virtual concatenations across component removal James Hilliard
2026-11-12 3:09 ` sashiko-bot
2026-11-12 1:27 ` [PATCH v3 05/58] mtd: rawnand: separate hardware disconnection from cleanup James Hilliard
2026-11-12 1:27 ` [PATCH v3 06/58] mtd: rawnand: share retained chip and controller ownership James Hilliard
2026-11-12 1:27 ` [PATCH v3 07/58] mtd: rawnand: sunxi: retain chip state until the final MTD put James Hilliard
2026-11-12 1:27 ` [PATCH v3 08/58] mtd: rawnand: plat_nand: retain open-handle state on removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 09/58] mtd: rawnand: gpio: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 10/58] mtd: rawnand: stm32_fmc2: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 11/58] mtd: rawnand: denali: retain chips across controller removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 12/58] mtd: rawnand: arasan: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 13/58] mtd: rawnand: mxic: retain open-handle state on removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 14/58] mtd: rawnand: intel: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 15/58] mtd: rawnand: tegra: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 16/58] mtd: rawnand: mtk: retain chips across controller removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 17/58] mtd: rawnand: rockchip: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 18/58] mtd: rawnand: atmel: " James Hilliard
2026-11-12 1:27 ` [PATCH v3 19/58] mtd: rawnand: orion: retain open-handle state on removal James Hilliard
2026-11-12 1:27 ` [PATCH v3 20/58] mtd: rawnand: socrates: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 21/58] mtd: rawnand: fsl_upm: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 22/58] mtd: rawnand: pasemi: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 23/58] mtd: rawnand: ams-delta: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 24/58] mtd: rawnand: xway: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 25/58] mtd: rawnand: hisi504: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 26/58] mtd: rawnand: vf610: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 27/58] mtd: rawnand: technologic: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 28/58] mtd: rawnand: loongson: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 29/58] mtd: rawnand: ma35d1: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 30/58] mtd: rawnand: pl35x: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 31/58] mtd: rawnand: mxc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 32/58] mtd: rawnand: fsmc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 33/58] mtd: rawnand: gpmi: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 34/58] mtd: rawnand: omap2: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 35/58] mtd: rawnand: marvell: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 36/58] mtd: rawnand: renesas: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 37/58] mtd: rawnand: meson: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 38/58] mtd: rawnand: cadence: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 39/58] mtd: rawnand: qcom: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 40/58] mtd: rawnand: cafe: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 41/58] mtd: rawnand: brcmnand: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 42/58] mtd: rawnand: au1550nd: retain chip state across removal James Hilliard
2026-11-12 1:28 ` [PATCH v3 43/58] mtd: rawnand: sharpsl: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 44/58] mtd: rawnand: davinci: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 45/58] mtd: rawnand: sh_flctl: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 46/58] mtd: rawnand: lpc32xx_slc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 47/58] mtd: rawnand: lpc32xx_mlc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 48/58] mtd: rawnand: ndfc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 49/58] mtd: rawnand: txx9ndfmc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 50/58] mtd: rawnand: ingenic: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 51/58] mtd: rawnand: mpc5121: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 52/58] mtd: rawnand: bcm47xxnflash: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 53/58] mtd: rawnand: fsl_elbc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 54/58] mtd: rawnand: fsl_ifc: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 55/58] mtd: rawnand: r852: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 56/58] mtd: rawnand: nandsim: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 57/58] mtd: rawnand: cs553x: " James Hilliard
2026-11-12 1:28 ` [PATCH v3 58/58] mtd: rawnand: diskonchip: " James Hilliard
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox