@@ -45,6 +45,9 @@
#define SPI_NOR_SRST_SLEEP_MIN 200
#define SPI_NOR_SRST_SLEEP_MAX 400
+static void spi_nor_unlock_and_unprep_pe(struct spi_nor *nor, loff_t start, size_t len);
+static void spi_nor_unlock_and_unprep_rd(struct spi_nor *nor, loff_t start, size_t len);
+
/**
* spi_nor_get_cmd_ext() - Get the command opcode extension based on the
* extension type.
@@ -1345,8 +1348,15 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
else
ret = wait_event_killable(nor->rww.wait,
spi_nor_rww_start_exclusive(nor));
+ if (ret)
+ return ret;
- return ret;
+ if (nor->removed) {
+ spi_nor_unlock_and_unprep(nor);
+ return -ENODEV;
+ }
+
+ return 0;
}
void spi_nor_unlock_and_unprep(struct spi_nor *nor)
@@ -1416,8 +1426,15 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
else
ret = wait_event_killable(nor->rww.wait,
spi_nor_rww_start_pe(nor, start, len));
+ if (ret)
+ return ret;
- return ret;
+ if (nor->removed) {
+ spi_nor_unlock_and_unprep_pe(nor, start, len);
+ return -ENODEV;
+ }
+
+ return 0;
}
static void spi_nor_unlock_and_unprep_pe(struct spi_nor *nor, loff_t start, size_t len)
@@ -1489,8 +1506,15 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
else
ret = wait_event_killable(nor->rww.wait,
spi_nor_rww_start_rd(nor, start, len));
+ if (ret)
+ return ret;
- return ret;
+ if (nor->removed) {
+ spi_nor_unlock_and_unprep_rd(nor, start, len);
+ return -ENODEV;
+ }
+
+ return 0;
}
static void spi_nor_unlock_and_unprep_rd(struct spi_nor *nor, loff_t start, size_t len)
@@ -3186,7 +3210,12 @@ static int spi_nor_init_params(struct spi_nor *nor)
{
int ret;
- nor->params = devm_kzalloc(nor->dev, sizeof(*nor->params), GFP_KERNEL);
+ /* Keep params on the kref lifetime so it survives unbind (see probe). */
+ if (nor->refcounted)
+ nor->params = kzalloc(sizeof(*nor->params), GFP_KERNEL);
+ else
+ nor->params = devm_kzalloc(nor->dev, sizeof(*nor->params),
+ GFP_KERNEL);
if (!nor->params)
return -ENOMEM;
@@ -3420,6 +3449,22 @@ static void spi_nor_resume(struct mtd_info *mtd)
dev_err(dev, "resume() failed\n");
}
+static void spi_nor_release(struct kref *kref)
+{
+ struct spi_nor *nor = container_of(kref, struct spi_nor, refcount);
+
+ kfree(nor->bouncebuf);
+ kfree(nor->params);
+ kfree(nor);
+}
+
+static void spi_nor_release_device(void *data)
+{
+ struct spi_nor *nor = data;
+
+ kref_put(&nor->refcount, spi_nor_release);
+}
+
static int spi_nor_get_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
@@ -3434,6 +3479,12 @@ static int spi_nor_get_device(struct mtd_info *mtd)
if (!try_module_get(dev->driver->owner))
return -ENODEV;
+ if (nor->refcounted) {
+ /* Cache the module: the spimem/controller chain may be freed by put time. */
+ nor->controller_module = dev->driver->owner;
+ kref_get(&nor->refcount);
+ }
+
return 0;
}
@@ -3443,6 +3494,14 @@ static void spi_nor_put_device(struct mtd_info *mtd)
struct spi_nor *nor = mtd_to_spi_nor(master);
struct device *dev;
+ if (nor->refcounted) {
+ module_put(nor->controller_module);
+
+ /* Must be last: this may free nor (and the embedded mtd). */
+ kref_put(&nor->refcount, spi_nor_release);
+ return;
+ }
+
if (nor->spimem)
dev = nor->spimem->spi->controller->dev.parent;
else
@@ -3655,8 +3714,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
* than 1KB) after spi_nor_scan() returns.
*/
nor->bouncebuf_size = PAGE_SIZE;
- nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size,
- GFP_KERNEL);
+ if (nor->refcounted)
+ nor->bouncebuf = kmalloc(nor->bouncebuf_size, GFP_KERNEL);
+ else
+ nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size,
+ GFP_KERNEL);
if (!nor->bouncebuf)
return -ENOMEM;
@@ -3788,10 +3850,21 @@ static int spi_nor_probe(struct spi_mem *spimem)
if (ret)
return ret;
- nor = devm_kzalloc(dev, sizeof(*nor), GFP_KERNEL);
+ /*
+ * An open /dev/mtdX handle can outlive unbind, so manage the spi_nor
+ * with a kref and drop the probe-time reference from a devres callback.
+ */
+ nor = kzalloc_obj(*nor, GFP_KERNEL);
if (!nor)
return -ENOMEM;
+ kref_init(&nor->refcount);
+ nor->refcounted = true;
+
+ ret = devm_add_action_or_reset(dev, spi_nor_release_device, nor);
+ if (ret)
+ return ret;
+
nor->spimem = spimem;
nor->dev = dev;
spi_nor_set_flash_node(nor, dev->of_node);
@@ -3830,9 +3903,8 @@ static int spi_nor_probe(struct spi_mem *spimem)
*/
if (nor->params->page_size > PAGE_SIZE) {
nor->bouncebuf_size = nor->params->page_size;
- devm_kfree(dev, nor->bouncebuf);
- nor->bouncebuf = devm_kmalloc(dev, nor->bouncebuf_size,
- GFP_KERNEL);
+ kfree(nor->bouncebuf);
+ nor->bouncebuf = kmalloc(nor->bouncebuf_size, GFP_KERNEL);
if (!nor->bouncebuf)
return -ENOMEM;
}
@@ -3853,6 +3925,25 @@ static int spi_nor_remove(struct spi_mem *spimem)
{
struct spi_nor *nor = spi_mem_get_drvdata(spimem);
+ /*
+ * Drain in-flight operations and set nor->removed under the lock so
+ * later ones fail with -ENODEV before touching SPI-core state (spimem,
+ * dirmaps) freed after this returns. The wait is uninterruptible.
+ */
+ if (!spi_nor_use_parallel_locking(nor))
+ mutex_lock(&nor->lock);
+ else
+ wait_event(nor->rww.wait, spi_nor_rww_start_exclusive(nor));
+
+ nor->removed = true;
+
+ if (!spi_nor_use_parallel_locking(nor)) {
+ mutex_unlock(&nor->lock);
+ } else {
+ spi_nor_rww_end_exclusive(nor);
+ wake_up(&nor->rww.wait);
+ }
+
spi_nor_restore(nor);
/* Clean up MTD stuff. */
@@ -7,6 +7,7 @@
#define __LINUX_MTD_SPI_NOR_H
#include <linux/bitops.h>
+#include <linux/kref.h>
#include <linux/mtd/mtd.h>
#include <linux/spi/spi-mem.h>
@@ -352,6 +353,18 @@ struct spi_nor_flash_parameter;
* @rww.used_banks: bitmap of the banks in use
* @dev: pointer to an SPI device or an SPI NOR controller device
* @spimem: pointer to the SPI memory device
+ * @refcount: reference count keeping the kzalloc()'d spi_nor alive
+ * past driver unbind until the last MTD user releases the
+ * device. Only valid when @refcounted is set.
+ * @controller_module: controller module pinned in spi_nor_get_device() so
+ * spi_nor_put_device() need not walk the possibly freed
+ * spimem/controller chain. Only valid when @refcounted is
+ * set.
+ * @refcounted: true when the spi_nor lifetime is kref-managed (the
+ * spi-mem spi_nor_probe() path). Legacy controllers that
+ * embed or devres-allocate spi_nor leave this clear.
+ * @removed: set on unbind to make subsequent MTD operations fail
+ * with -ENODEV instead of touching released resources.
* @bouncebuf: bounce buffer used when the buffer passed by the MTD
* layer is not DMA-able
* @bouncebuf_size: size of the bounce buffer
@@ -393,6 +406,10 @@ struct spi_nor {
} rww;
struct device *dev;
struct spi_mem *spimem;
+ struct kref refcount;
+ struct module *controller_module;
+ bool refcounted;
+ bool removed;
u8 *bouncebuf;
size_t bouncebuf_size;
u8 *id;