summaryrefslogtreecommitdiff
path: root/drivers/mtd/mtdcore.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/mtdcore.c')
-rw-r--r--drivers/mtd/mtdcore.c192
1 files changed, 143 insertions, 49 deletions
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index e00b12aa5ec9..64808493b4f5 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -30,6 +30,7 @@
#include <linux/debugfs.h>
#include <linux/nvmem-provider.h>
#include <linux/root_dev.h>
+#include <linux/error-injection.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
@@ -93,10 +94,39 @@ static void mtd_release(struct device *dev)
struct mtd_info *mtd = dev_get_drvdata(dev);
dev_t index = MTD_DEVT(mtd->index);
+ idr_remove(&mtd_idr, mtd->index);
+ of_node_put(mtd_get_of_node(mtd));
+
+ if (mtd_is_partition(mtd))
+ release_mtd_partition(mtd);
+
/* remove /dev/mtdXro node */
device_destroy(&mtd_class, index + 1);
}
+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);
+
+ 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);
+}
+
#define MTD_DEVICE_ATTR_RO(name) \
static DEVICE_ATTR(name, 0444, mtd_##name##_show, NULL)
@@ -354,14 +384,64 @@ EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode);
static struct dentry *dfs_dir_mtd;
+static int mtd_ooblayout_show(struct seq_file *s, void *p,
+ int (*iter)(struct mtd_info *, int section,
+ struct mtd_oob_region *region))
+{
+ struct mtd_info *mtd = s->private;
+ int section;
+
+ for (section = 0;; section++) {
+ struct mtd_oob_region region;
+ int err;
+
+ err = iter(mtd, section, &region);
+ if (err) {
+ if (err == -ERANGE)
+ break;
+
+ return err;
+ }
+
+ seq_printf(s, "%-3d %4u %4u\n", section, region.offset,
+ region.length);
+ }
+
+ return 0;
+}
+
+static int mtd_ooblayout_ecc_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_ecc);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_ecc);
+
+static int mtd_ooblayout_free_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_free);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_free);
+
static void mtd_debugfs_populate(struct mtd_info *mtd)
{
struct device *dev = &mtd->dev;
+ struct mtd_oob_region region;
if (IS_ERR_OR_NULL(dfs_dir_mtd))
return;
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
+ if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir))
+ return;
+
+ /* Create ooblayout files only if at least one region is present. */
+ if (mtd_ooblayout_ecc(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_ecc", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_ecc_fops);
+
+ if (mtd_ooblayout_free(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_free", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_free_fops);
}
#ifndef CONFIG_MMU
@@ -523,6 +603,7 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
config.dev = &mtd->dev;
config.name = dev_name(&mtd->dev);
config.owner = THIS_MODULE;
+ config.add_legacy_fixed_of_cells = of_device_is_compatible(node, "nvmem-cells");
config.reg_read = mtd_nvmem_reg_read;
config.size = mtd->size;
config.word_size = 1;
@@ -530,7 +611,6 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
config.read_only = true;
config.root_only = true;
config.ignore_wp = true;
- config.no_of_node = !of_device_is_compatible(node, "nvmem-cells");
config.priv = mtd;
mtd->nvmem = nvmem_register(&config);
@@ -591,6 +671,7 @@ static void mtd_check_of_node(struct mtd_info *mtd)
if (plen == mtd_name_len &&
!strncmp(mtd->name, pname + offset, plen)) {
mtd_set_of_node(mtd, mtd_dn);
+ of_node_put(mtd_dn);
break;
}
}
@@ -666,7 +747,7 @@ int add_mtd_device(struct mtd_info *mtd)
}
mtd->index = i;
- mtd->usecount = 0;
+ kref_init(&mtd->refcnt);
/* default value if not set by driver */
if (mtd->bitflip_threshold == 0)
@@ -710,7 +791,9 @@ int add_mtd_device(struct mtd_info *mtd)
mtd->dev.type = &mtd_devtype;
mtd->dev.class = &mtd_class;
mtd->dev.devt = MTD_DEVT(i);
- dev_set_name(&mtd->dev, "mtd%d", i);
+ error = dev_set_name(&mtd->dev, "mtd%d", i);
+ if (error)
+ goto fail_devname;
dev_set_drvdata(&mtd->dev, mtd);
mtd_check_of_node(mtd);
of_node_get(mtd_get_of_node(mtd));
@@ -759,6 +842,7 @@ fail_nvmem_add:
device_unregister(&mtd->dev);
fail_added:
of_node_put(mtd_get_of_node(mtd));
+fail_devname:
idr_remove(&mtd_idr, i);
fail_locked:
mutex_unlock(&mtd_table_mutex);
@@ -779,7 +863,6 @@ int del_mtd_device(struct mtd_info *mtd)
{
int ret;
struct mtd_notifier *not;
- struct device_node *mtd_of_node;
mutex_lock(&mtd_table_mutex);
@@ -793,28 +876,8 @@ int del_mtd_device(struct mtd_info *mtd)
list_for_each_entry(not, &mtd_notifiers, list)
not->remove(mtd);
- if (mtd->usecount) {
- printk(KERN_NOTICE "Removing MTD device #%d (%s) with use count %d\n",
- mtd->index, mtd->name, mtd->usecount);
- ret = -EBUSY;
- } else {
- mtd_of_node = mtd_get_of_node(mtd);
- 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 */
- memset(&mtd->dev, 0, sizeof(mtd->dev));
-
- idr_remove(&mtd_idr, mtd->index);
- of_node_put(mtd_of_node);
-
- module_put(THIS_MODULE);
- ret = 0;
- }
+ kref_put(&mtd->refcnt, mtd_device_release);
+ ret = 0;
out_error:
mutex_unlock(&mtd_table_mutex);
@@ -890,6 +953,7 @@ static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
config.name = compatible;
config.id = NVMEM_DEVID_AUTO;
config.owner = THIS_MODULE;
+ config.add_legacy_fixed_of_cells = !mtd_type_is_nand(mtd);
config.type = NVMEM_TYPE_OTP;
config.root_only = true;
config.ignore_wp = true;
@@ -945,8 +1009,10 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd)
if (mtd->_get_user_prot_info && mtd->_read_user_prot_reg) {
size = mtd_otp_size(mtd, true);
- if (size < 0)
- return size;
+ if (size < 0) {
+ err = size;
+ goto err;
+ }
if (size > 0) {
nvmem = mtd_otp_nvmem_register(mtd, "user-otp", size,
@@ -1001,6 +1067,9 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd)
err:
nvmem_unregister(mtd->otp_user_nvmem);
+ /* Don't report error if OTP is not supported. */
+ if (err == -EOPNOTSUPP)
+ return 0;
return dev_err_probe(dev, err, "Failed to register OTP NVMEM device\n");
}
@@ -1037,7 +1106,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
const struct mtd_partition *parts,
int nr_parts)
{
- int ret;
+ int ret, err;
mtd_set_dev_defaults(mtd);
@@ -1089,8 +1158,11 @@ out:
nvmem_unregister(mtd->otp_factory_nvmem);
}
- if (ret && device_is_registered(&mtd->dev))
- del_mtd_device(mtd);
+ if (ret && device_is_registered(&mtd->dev)) {
+ err = del_mtd_device(mtd);
+ if (err)
+ pr_err("Error when deleting MTD device (%d)\n", err);
+ }
return ret;
}
@@ -1227,25 +1299,27 @@ int __get_mtd_device(struct mtd_info *mtd)
struct mtd_info *master = mtd_get_master(mtd);
int err;
- if (!try_module_get(master->owner))
- return -ENODEV;
-
if (master->_get_device) {
err = master->_get_device(mtd);
-
- if (err) {
- module_put(master->owner);
+ if (err)
return err;
- }
}
- master->usecount++;
+ if (!try_module_get(master->owner)) {
+ if (master->_put_device)
+ master->_put_device(master);
+ return -ENODEV;
+ }
- while (mtd->parent) {
- mtd->usecount++;
+ while (mtd) {
+ if (mtd != master)
+ kref_get(&mtd->refcnt);
mtd = mtd->parent;
}
+ if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+ kref_get(&master->refcnt);
+
return 0;
}
EXPORT_SYMBOL_GPL(__get_mtd_device);
@@ -1329,18 +1403,23 @@ void __put_mtd_device(struct mtd_info *mtd)
{
struct mtd_info *master = mtd_get_master(mtd);
- while (mtd->parent) {
- --mtd->usecount;
- BUG_ON(mtd->usecount < 0);
- mtd = mtd->parent;
+ while (mtd) {
+ /* kref_put() can relese mtd, so keep a reference mtd->parent */
+ struct mtd_info *parent = mtd->parent;
+
+ if (mtd != master)
+ kref_put(&mtd->refcnt, mtd_device_release);
+ mtd = parent;
}
- master->usecount--;
+ if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
+ kref_put(&master->refcnt, mtd_device_release);
+
+ module_put(master->owner);
+ /* must be the last as master can be freed in the _put_device */
if (master->_put_device)
master->_put_device(master);
-
- module_put(master->owner);
}
EXPORT_SYMBOL_GPL(__put_mtd_device);
@@ -1396,6 +1475,7 @@ int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
return ret;
}
EXPORT_SYMBOL_GPL(mtd_erase);
+ALLOW_ERROR_INJECTION(mtd_erase, ERRNO);
/*
* This stuff for eXecute-In-Place. phys is optional and may be set to NULL.
@@ -1490,9 +1570,12 @@ int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
ret = mtd_read_oob(mtd, from, &ops);
*retlen = ops.retlen;
+ WARN_ON_ONCE(*retlen != len && mtd_is_bitflip_or_eccerr(ret));
+
return ret;
}
EXPORT_SYMBOL_GPL(mtd_read);
+ALLOW_ERROR_INJECTION(mtd_read, ERRNO);
int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
const u_char *buf)
@@ -1509,6 +1592,7 @@ int mtd_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
return ret;
}
EXPORT_SYMBOL_GPL(mtd_write);
+ALLOW_ERROR_INJECTION(mtd_write, ERRNO);
/*
* In blackbox flight recorder like scenarios we want to make successful writes
@@ -2305,6 +2389,7 @@ EXPORT_SYMBOL_GPL(mtd_block_isbad);
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
struct mtd_info *master = mtd_get_master(mtd);
+ loff_t moffs;
int ret;
if (!master->_block_markbad)
@@ -2317,7 +2402,15 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
if (mtd->flags & MTD_SLC_ON_MLC_EMULATION)
ofs = (loff_t)mtd_div_by_eb(ofs, mtd) * master->erasesize;
- ret = master->_block_markbad(master, mtd_get_master_ofs(mtd, ofs));
+ moffs = mtd_get_master_ofs(mtd, ofs);
+
+ if (master->_block_isbad) {
+ ret = master->_block_isbad(master, moffs);
+ if (ret > 0)
+ return 0;
+ }
+
+ ret = master->_block_markbad(master, moffs);
if (ret)
return ret;
@@ -2329,6 +2422,7 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
return 0;
}
EXPORT_SYMBOL_GPL(mtd_block_markbad);
+ALLOW_ERROR_INJECTION(mtd_block_markbad, ERRNO);
/*
* default_mtd_writev - the default writev method