[PATCH v4 41/58] mtd: rawnand: brcmnand: retain open-handle state on removal

From: James Hilliard

Date: Fri Nov 13 2026 - 15:44:35 EST


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@xxxxxxxxxxxx>
Cc: Brian Norris <computersforpeace@xxxxxxxxx>
Cc: Kamal Dasu <kamal.dasu@xxxxxxxxxxxx>
Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
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