mtd: core: always create master device

Create master device without partition when
CONFIG_MTD_PARTITIONED_MASTER flag is unset.

This streamlines device tree and allows to anchor
runtime power management on master device in all cases.

Signed-off-by: Alexander Usyskin <alexander.usyskin@intel.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
Alexander Usyskin 2025-04-24 16:25:25 +03:00 committed by Miquel Raynal
parent 91b7163b1f
commit 0aa7b390fc
5 changed files with 123 additions and 51 deletions

View file

@ -559,7 +559,7 @@ static int mtdchar_blkpg_ioctl(struct mtd_info *mtd,
/* Sanitize user input */ /* Sanitize user input */
p.devname[BLKPG_DEVNAMELTH - 1] = '\0'; p.devname[BLKPG_DEVNAMELTH - 1] = '\0';
return mtd_add_partition(mtd, p.devname, p.start, p.length); return mtd_add_partition(mtd, p.devname, p.start, p.length, NULL);
case BLKPG_DEL_PARTITION: case BLKPG_DEL_PARTITION:

View file

@ -68,7 +68,13 @@ static struct class mtd_class = {
.pm = MTD_CLS_PM_OPS, .pm = MTD_CLS_PM_OPS,
}; };
static struct class mtd_master_class = {
.name = "mtd_master",
.pm = MTD_CLS_PM_OPS,
};
static DEFINE_IDR(mtd_idr); static DEFINE_IDR(mtd_idr);
static DEFINE_IDR(mtd_master_idr);
/* These are exported solely for the purpose of mtd_blkdevs.c. You /* These are exported solely for the purpose of mtd_blkdevs.c. You
should not use them for _anything_ else */ should not use them for _anything_ else */
@ -83,8 +89,9 @@ EXPORT_SYMBOL_GPL(__mtd_next_device);
static LIST_HEAD(mtd_notifiers); static LIST_HEAD(mtd_notifiers);
#define MTD_MASTER_DEVS 255
#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2) #define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
static dev_t mtd_master_devt;
/* REVISIT once MTD uses the driver model better, whoever allocates /* REVISIT once MTD uses the driver model better, whoever allocates
* the mtd_info will probably want to use the release() hook... * the mtd_info will probably want to use the release() hook...
@ -104,6 +111,17 @@ static void mtd_release(struct device *dev)
device_destroy(&mtd_class, index + 1); device_destroy(&mtd_class, index + 1);
} }
static void mtd_master_release(struct device *dev)
{
struct mtd_info *mtd = dev_get_drvdata(dev);
idr_remove(&mtd_master_idr, mtd->index);
of_node_put(mtd_get_of_node(mtd));
if (mtd_is_partition(mtd))
release_mtd_partition(mtd);
}
static void mtd_device_release(struct kref *kref) static void mtd_device_release(struct kref *kref)
{ {
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt); struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
@ -367,6 +385,11 @@ static const struct device_type mtd_devtype = {
.release = mtd_release, .release = mtd_release,
}; };
static const struct device_type mtd_master_devtype = {
.name = "mtd_master",
.release = mtd_master_release,
};
static bool mtd_expert_analysis_mode; static bool mtd_expert_analysis_mode;
#ifdef CONFIG_DEBUG_FS #ifdef CONFIG_DEBUG_FS
@ -634,13 +657,13 @@ exit_parent:
/** /**
* add_mtd_device - register an MTD device * add_mtd_device - register an MTD device
* @mtd: pointer to new MTD device info structure * @mtd: pointer to new MTD device info structure
* @partitioned: create partitioned device
* *
* Add a device to the list of MTD devices present in the system, and * Add a device to the list of MTD devices present in the system, and
* notify each currently active MTD 'user' of its arrival. Returns * notify each currently active MTD 'user' of its arrival. Returns
* zero on success or non-zero on failure. * zero on success or non-zero on failure.
*/ */
int add_mtd_device(struct mtd_info *mtd, bool partitioned)
int add_mtd_device(struct mtd_info *mtd)
{ {
struct device_node *np = mtd_get_of_node(mtd); struct device_node *np = mtd_get_of_node(mtd);
struct mtd_info *master = mtd_get_master(mtd); struct mtd_info *master = mtd_get_master(mtd);
@ -687,10 +710,17 @@ int add_mtd_device(struct mtd_info *mtd)
ofidx = -1; ofidx = -1;
if (np) if (np)
ofidx = of_alias_get_id(np, "mtd"); ofidx = of_alias_get_id(np, "mtd");
if (ofidx >= 0) if (partitioned) {
i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL); if (ofidx >= 0)
else i = idr_alloc(&mtd_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL);
i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL); else
i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
} else {
if (ofidx >= 0)
i = idr_alloc(&mtd_master_idr, mtd, ofidx, ofidx + 1, GFP_KERNEL);
else
i = idr_alloc(&mtd_master_idr, mtd, 0, 0, GFP_KERNEL);
}
if (i < 0) { if (i < 0) {
error = i; error = i;
goto fail_locked; goto fail_locked;
@ -738,10 +768,18 @@ int add_mtd_device(struct mtd_info *mtd)
/* Caller should have set dev.parent to match the /* Caller should have set dev.parent to match the
* physical device, if appropriate. * physical device, if appropriate.
*/ */
mtd->dev.type = &mtd_devtype; if (partitioned) {
mtd->dev.class = &mtd_class; mtd->dev.type = &mtd_devtype;
mtd->dev.devt = MTD_DEVT(i); mtd->dev.class = &mtd_class;
error = dev_set_name(&mtd->dev, "mtd%d", i); mtd->dev.devt = MTD_DEVT(i);
dev_set_name(&mtd->dev, "mtd%d", i);
error = dev_set_name(&mtd->dev, "mtd%d", i);
} else {
mtd->dev.type = &mtd_master_devtype;
mtd->dev.class = &mtd_master_class;
mtd->dev.devt = MKDEV(MAJOR(mtd_master_devt), i);
error = dev_set_name(&mtd->dev, "mtd_master%d", i);
}
if (error) if (error)
goto fail_devname; goto fail_devname;
dev_set_drvdata(&mtd->dev, mtd); dev_set_drvdata(&mtd->dev, mtd);
@ -749,6 +787,7 @@ int add_mtd_device(struct mtd_info *mtd)
of_node_get(mtd_get_of_node(mtd)); of_node_get(mtd_get_of_node(mtd));
error = device_register(&mtd->dev); error = device_register(&mtd->dev);
if (error) { if (error) {
pr_err("mtd: %s device_register fail %d\n", mtd->name, error);
put_device(&mtd->dev); put_device(&mtd->dev);
goto fail_added; goto fail_added;
} }
@ -760,10 +799,13 @@ int add_mtd_device(struct mtd_info *mtd)
mtd_debugfs_populate(mtd); mtd_debugfs_populate(mtd);
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL, if (partitioned) {
"mtd%dro", i); device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
"mtd%dro", i);
}
pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name); pr_debug("mtd: Giving out %spartitioned device %d to %s\n",
partitioned ? "" : "un-", i, mtd->name);
/* No need to get a refcount on the module containing /* No need to get a refcount on the module containing
the notifier, since we hold the mtd_table_mutex */ the notifier, since we hold the mtd_table_mutex */
list_for_each_entry(not, &mtd_notifiers, list) list_for_each_entry(not, &mtd_notifiers, list)
@ -771,13 +813,16 @@ int add_mtd_device(struct mtd_info *mtd)
mutex_unlock(&mtd_table_mutex); mutex_unlock(&mtd_table_mutex);
if (of_property_read_bool(mtd_get_of_node(mtd), "linux,rootfs")) { if (partitioned) {
if (IS_BUILTIN(CONFIG_MTD)) { if (of_property_read_bool(mtd_get_of_node(mtd), "linux,rootfs")) {
pr_info("mtd: setting mtd%d (%s) as root device\n", mtd->index, mtd->name); if (IS_BUILTIN(CONFIG_MTD)) {
ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index); pr_info("mtd: setting mtd%d (%s) as root device\n",
} else { mtd->index, mtd->name);
pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n", ROOT_DEV = MKDEV(MTD_BLOCK_MAJOR, mtd->index);
mtd->index, mtd->name); } else {
pr_warn("mtd: can't set mtd%d (%s) as root device - mtd must be builtin\n",
mtd->index, mtd->name);
}
} }
} }
@ -793,7 +838,10 @@ fail_nvmem_add:
fail_added: fail_added:
of_node_put(mtd_get_of_node(mtd)); of_node_put(mtd_get_of_node(mtd));
fail_devname: fail_devname:
idr_remove(&mtd_idr, i); if (partitioned)
idr_remove(&mtd_idr, i);
else
idr_remove(&mtd_master_idr, i);
fail_locked: fail_locked:
mutex_unlock(&mtd_table_mutex); mutex_unlock(&mtd_table_mutex);
return error; return error;
@ -811,12 +859,14 @@ fail_locked:
int del_mtd_device(struct mtd_info *mtd) int del_mtd_device(struct mtd_info *mtd)
{ {
int ret;
struct mtd_notifier *not; struct mtd_notifier *not;
struct idr *idr;
int ret;
mutex_lock(&mtd_table_mutex); mutex_lock(&mtd_table_mutex);
if (idr_find(&mtd_idr, mtd->index) != mtd) { idr = mtd->dev.class == &mtd_class ? &mtd_idr : &mtd_master_idr;
if (idr_find(idr, mtd->index) != mtd) {
ret = -ENODEV; ret = -ENODEV;
goto out_error; goto out_error;
} }
@ -1056,6 +1106,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
const struct mtd_partition *parts, const struct mtd_partition *parts,
int nr_parts) int nr_parts)
{ {
struct mtd_info *parent;
int ret, err; int ret, err;
mtd_set_dev_defaults(mtd); mtd_set_dev_defaults(mtd);
@ -1064,25 +1115,30 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
if (ret) if (ret)
goto out; goto out;
ret = add_mtd_device(mtd, false);
if (ret)
goto out;
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) { if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) {
ret = add_mtd_device(mtd); ret = mtd_add_partition(mtd, mtd->name, 0, MTDPART_SIZ_FULL, &parent);
if (ret) if (ret)
goto out; goto out;
} else {
parent = mtd;
} }
/* Prefer parsed partitions over driver-provided fallback */ /* Prefer parsed partitions over driver-provided fallback */
ret = parse_mtd_partitions(mtd, types, parser_data); ret = parse_mtd_partitions(parent, types, parser_data);
if (ret == -EPROBE_DEFER) if (ret == -EPROBE_DEFER)
goto out; goto out;
if (ret > 0) if (ret > 0)
ret = 0; ret = 0;
else if (nr_parts) else if (nr_parts)
ret = add_mtd_partitions(mtd, parts, nr_parts); ret = add_mtd_partitions(parent, parts, nr_parts);
else if (!device_is_registered(&mtd->dev)) else if (!IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
ret = add_mtd_device(mtd); ret = mtd_add_partition(parent, mtd->name, 0, MTDPART_SIZ_FULL, NULL);
else
ret = 0;
if (ret) if (ret)
goto out; goto out;
@ -1102,13 +1158,14 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
register_reboot_notifier(&mtd->reboot_notifier); register_reboot_notifier(&mtd->reboot_notifier);
} }
return 0;
out: out:
if (ret) { nvmem_unregister(mtd->otp_user_nvmem);
nvmem_unregister(mtd->otp_user_nvmem); nvmem_unregister(mtd->otp_factory_nvmem);
nvmem_unregister(mtd->otp_factory_nvmem);
}
if (ret && device_is_registered(&mtd->dev)) { del_mtd_partitions(mtd);
if (device_is_registered(&mtd->dev)) {
err = del_mtd_device(mtd); err = del_mtd_device(mtd);
if (err) if (err)
pr_err("Error when deleting MTD device (%d)\n", err); pr_err("Error when deleting MTD device (%d)\n", err);
@ -1267,8 +1324,7 @@ int __get_mtd_device(struct mtd_info *mtd)
mtd = mtd->parent; mtd = mtd->parent;
} }
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) kref_get(&master->refcnt);
kref_get(&master->refcnt);
return 0; return 0;
} }
@ -1362,8 +1418,7 @@ void __put_mtd_device(struct mtd_info *mtd)
mtd = parent; mtd = parent;
} }
if (IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) kref_put(&master->refcnt, mtd_device_release);
kref_put(&master->refcnt, mtd_device_release);
module_put(master->owner); module_put(master->owner);
@ -2530,6 +2585,16 @@ static int __init init_mtd(void)
if (ret) if (ret)
goto err_reg; goto err_reg;
ret = class_register(&mtd_master_class);
if (ret)
goto err_reg2;
ret = alloc_chrdev_region(&mtd_master_devt, 0, MTD_MASTER_DEVS, "mtd_master");
if (ret < 0) {
pr_err("unable to allocate char dev region\n");
goto err_chrdev;
}
mtd_bdi = mtd_bdi_init("mtd"); mtd_bdi = mtd_bdi_init("mtd");
if (IS_ERR(mtd_bdi)) { if (IS_ERR(mtd_bdi)) {
ret = PTR_ERR(mtd_bdi); ret = PTR_ERR(mtd_bdi);
@ -2554,6 +2619,10 @@ out_procfs:
bdi_unregister(mtd_bdi); bdi_unregister(mtd_bdi);
bdi_put(mtd_bdi); bdi_put(mtd_bdi);
err_bdi: err_bdi:
unregister_chrdev_region(mtd_master_devt, MTD_MASTER_DEVS);
err_chrdev:
class_unregister(&mtd_master_class);
err_reg2:
class_unregister(&mtd_class); class_unregister(&mtd_class);
err_reg: err_reg:
pr_err("Error registering mtd class or bdi: %d\n", ret); pr_err("Error registering mtd class or bdi: %d\n", ret);
@ -2567,9 +2636,12 @@ static void __exit cleanup_mtd(void)
if (proc_mtd) if (proc_mtd)
remove_proc_entry("mtd", NULL); remove_proc_entry("mtd", NULL);
class_unregister(&mtd_class); class_unregister(&mtd_class);
class_unregister(&mtd_master_class);
unregister_chrdev_region(mtd_master_devt, MTD_MASTER_DEVS);
bdi_unregister(mtd_bdi); bdi_unregister(mtd_bdi);
bdi_put(mtd_bdi); bdi_put(mtd_bdi);
idr_destroy(&mtd_idr); idr_destroy(&mtd_idr);
idr_destroy(&mtd_master_idr);
} }
module_init(init_mtd); module_init(init_mtd);

View file

@ -8,7 +8,7 @@ extern struct mutex mtd_table_mutex;
extern struct backing_dev_info *mtd_bdi; extern struct backing_dev_info *mtd_bdi;
struct mtd_info *__mtd_next_device(int i); struct mtd_info *__mtd_next_device(int i);
int __must_check add_mtd_device(struct mtd_info *mtd); int __must_check add_mtd_device(struct mtd_info *mtd, bool partitioned);
int del_mtd_device(struct mtd_info *mtd); int del_mtd_device(struct mtd_info *mtd);
int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int); int add_mtd_partitions(struct mtd_info *, const struct mtd_partition *, int);
int del_mtd_partitions(struct mtd_info *); int del_mtd_partitions(struct mtd_info *);

View file

@ -86,8 +86,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
* parent conditional on that option. Note, this is a way to * parent conditional on that option. Note, this is a way to
* distinguish between the parent and its partitions in sysfs. * distinguish between the parent and its partitions in sysfs.
*/ */
child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ? child->dev.parent = &parent->dev;
&parent->dev : parent->dev.parent;
child->dev.of_node = part->of_node; child->dev.of_node = part->of_node;
child->parent = parent; child->parent = parent;
child->part.offset = part->offset; child->part.offset = part->offset;
@ -243,7 +242,7 @@ static int mtd_add_partition_attrs(struct mtd_info *new)
} }
int mtd_add_partition(struct mtd_info *parent, const char *name, int mtd_add_partition(struct mtd_info *parent, const char *name,
long long offset, long long length) long long offset, long long length, struct mtd_info **out)
{ {
struct mtd_info *master = mtd_get_master(parent); struct mtd_info *master = mtd_get_master(parent);
u64 parent_size = mtd_is_partition(parent) ? u64 parent_size = mtd_is_partition(parent) ?
@ -276,12 +275,15 @@ int mtd_add_partition(struct mtd_info *parent, const char *name,
list_add_tail(&child->part.node, &parent->partitions); list_add_tail(&child->part.node, &parent->partitions);
mutex_unlock(&master->master.partitions_lock); mutex_unlock(&master->master.partitions_lock);
ret = add_mtd_device(child); ret = add_mtd_device(child, true);
if (ret) if (ret)
goto err_remove_part; goto err_remove_part;
mtd_add_partition_attrs(child); mtd_add_partition_attrs(child);
if (out)
*out = child;
return 0; return 0;
err_remove_part: err_remove_part:
@ -413,7 +415,7 @@ int add_mtd_partitions(struct mtd_info *parent,
list_add_tail(&child->part.node, &parent->partitions); list_add_tail(&child->part.node, &parent->partitions);
mutex_unlock(&master->master.partitions_lock); mutex_unlock(&master->master.partitions_lock);
ret = add_mtd_device(child); ret = add_mtd_device(child, true);
if (ret) { if (ret) {
mutex_lock(&master->master.partitions_lock); mutex_lock(&master->master.partitions_lock);
list_del(&child->part.node); list_del(&child->part.node);
@ -590,9 +592,6 @@ static int mtd_part_of_parse(struct mtd_info *master,
int ret, err = 0; 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;
np = mtd_get_of_node(master); np = mtd_get_of_node(master);
if (mtd_is_partition(master)) if (mtd_is_partition(master))
@ -711,6 +710,7 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
if (ret < 0 && !err) if (ret < 0 && !err)
err = ret; err = ret;
} }
return err; return err;
} }

View file

@ -108,7 +108,7 @@ extern void deregister_mtd_parser(struct mtd_part_parser *parser);
deregister_mtd_parser) deregister_mtd_parser)
int mtd_add_partition(struct mtd_info *master, const char *name, int mtd_add_partition(struct mtd_info *master, const char *name,
long long offset, long long length); long long offset, long long length, struct mtd_info **part);
int mtd_del_partition(struct mtd_info *master, int partno); int mtd_del_partition(struct mtd_info *master, int partno);
uint64_t mtd_get_device_size(const struct mtd_info *mtd); uint64_t mtd_get_device_size(const struct mtd_info *mtd);