summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2015-03-06 08:33:43 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2015-03-06 08:33:43 +0100
commit0e055c254c3eab40b52b3ac5f845a889bd281e05 (patch)
tree4f849bb3d50194ebc091b8be25b1b5072d73d063 /drivers
parent9524e7d0d7d22fbc9bde4fcebc7eeb6a12763c12 (diff)
parentfba2adcb3d50e86d4e26da9126c1ef6424e348e6 (diff)
downloadbarebox-0e055c254c3eab40b52b3ac5f845a889bd281e05.tar.gz
barebox-0e055c254c3eab40b52b3ac5f845a889bd281e05.tar.xz
Merge branch 'for-next/mtd-partitions'
Diffstat (limited to 'drivers')
-rw-r--r--drivers/mtd/core.c213
-rw-r--r--drivers/mtd/nand/nand-bb.c16
-rw-r--r--drivers/mtd/partition.c14
3 files changed, 234 insertions, 9 deletions
diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c
index 345752ecfe..63b1e4a824 100644
--- a/drivers/mtd/core.c
+++ b/drivers/mtd/core.c
@@ -18,6 +18,7 @@
#include <common.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/mtd.h>
+#include <cmdlinepart.h>
#include <init.h>
#include <xfuncs.h>
#include <driver.h>
@@ -377,9 +378,174 @@ static struct file_operations mtd_ops = {
.lseek = dev_lseek_default,
};
+static int mtd_partition_set(struct device_d *dev, struct param_d *p, const char *val)
+{
+ struct mtd_info *mtd = container_of(dev, struct mtd_info, class_dev);
+ struct mtd_info *mtdpart, *tmp;
+ int ret;
+
+ list_for_each_entry_safe(mtdpart, tmp, &mtd->partitions, partitions_entry) {
+ ret = mtd_del_partition(mtdpart);
+ if (ret)
+ return ret;
+ }
+
+ return cmdlinepart_do_parse(mtd->cdev.name, val, mtd->size, CMDLINEPART_ADD_DEVNAME);
+}
+
+static char *print_size(uint64_t s)
+{
+ if (!(s & ((1 << 20) - 1)))
+ return asprintf("%lldM", s >> 20);
+ if (!(s & ((1 << 10) - 1)))
+ return asprintf("%lldk", s >> 10);
+ return asprintf("0x%lld", s);
+}
+
+static int print_part(char *buf, int bufsize, struct mtd_info *mtd, uint64_t last_ofs,
+ int is_last)
+{
+ char *size = print_size(mtd->size);
+ char *ofs = print_size(mtd->master_offset);
+ int ret;
+
+ if (!size || !ofs) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (mtd->master_offset == last_ofs)
+ ret = snprintf(buf, bufsize, "%s(%s)%s", size,
+ mtd->cdev.partname,
+ is_last ? "" : ",");
+ else
+ ret = snprintf(buf, bufsize, "%s@%s(%s)%s", size,
+ ofs,
+ mtd->cdev.partname,
+ is_last ? "" : ",");
+out:
+ free(size);
+ free(ofs);
+
+ return ret;
+}
+
+static int print_parts(char *buf, int bufsize, struct mtd_info *mtd)
+{
+ struct mtd_info *mtdpart;
+ uint64_t last_ofs = 0;
+ int ret = 0;
+
+ list_for_each_entry(mtdpart, &mtd->partitions, partitions_entry) {
+ int now;
+ int is_last = list_is_last(&mtdpart->partitions_entry,
+ &mtd->partitions);
+
+ now = print_part(buf, bufsize, mtdpart, last_ofs, is_last);
+ if (now < 0)
+ return now;
+
+ if (buf && bufsize) {
+ buf += now;
+ bufsize -= now;
+ }
+ ret += now;
+ last_ofs = mtdpart->master_offset + mtdpart->size;
+ }
+
+ return ret;
+}
+
+static const char *mtd_partition_get(struct device_d *dev, struct param_d *p)
+{
+ struct mtd_info *mtd = container_of(dev, struct mtd_info, class_dev);
+ int len = 0;
+
+ free(p->value);
+
+ len = print_parts(NULL, 0, mtd);
+ p->value = xzalloc(len + 1);
+ print_parts(p->value, len + 1, mtd);
+
+ return p->value;
+}
+
+static int mtd_part_compare(struct list_head *a, struct list_head *b)
+{
+ struct mtd_info *mtda = container_of(a, struct mtd_info, partitions_entry);
+ struct mtd_info *mtdb = container_of(b, struct mtd_info, partitions_entry);
+
+ if (mtda->master_offset > mtdb->master_offset)
+ return 1;
+ if (mtda->master_offset < mtdb->master_offset)
+ return -1;
+ return 0;
+}
+
+static int of_mtd_fixup(struct device_node *root, void *ctx)
+{
+ struct mtd_info *mtd = ctx, *partmtd;
+ struct device_node *np, *part, *tmp;
+ int ret, i = 0;
+
+ np = of_find_node_by_path(mtd->of_path);
+ if (!np) {
+ dev_err(&mtd->class_dev, "Cannot find nodepath %s, cannot fixup\n",
+ mtd->of_path);
+ return -EINVAL;
+ }
+
+ for_each_child_of_node_safe(np, tmp, part) {
+ if (of_get_property(part, "compatible", NULL))
+ continue;
+ of_delete_node(part);
+ }
+
+ list_for_each_entry(partmtd, &mtd->partitions, partitions_entry) {
+ int na, ns, len = 0;
+ char *name = asprintf("partition@%d", i++);
+ void *p;
+ u8 tmp[16 * 16]; /* Up to 64-bit address + 64-bit size */
+
+ if (!name)
+ return -ENOMEM;
+
+ part = of_new_node(np, name);
+ free(name);
+ if (!part)
+ return -ENOMEM;
+
+ p = of_new_property(part, "label", partmtd->cdev.partname,
+ strlen(partmtd->cdev.partname) + 1);
+ if (!p)
+ return -ENOMEM;
+
+ na = of_n_addr_cells(np);
+ ns = of_n_size_cells(np);
+
+ of_write_number(tmp + len, partmtd->master_offset, na);
+ len += na * 4;
+ of_write_number(tmp + len, partmtd->size, ns);
+ len += ns * 4;
+
+ ret = of_set_property(part, "reg", tmp, len, 1);
+ if (ret)
+ return ret;
+
+ if (partmtd->cdev.flags & DEVFS_PARTITION_READONLY) {
+ ret = of_set_property(part, "read-only", NULL, 0, 1);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
{
struct mtddev_hook *hook;
+ int ret;
if (!devname)
devname = "mtd";
@@ -387,7 +553,10 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
mtd->class_dev.id = device_id;
if (mtd->parent)
mtd->class_dev.parent = mtd->parent;
- register_device(&mtd->class_dev);
+
+ ret = register_device(&mtd->class_dev);
+ if (ret)
+ return ret;
mtd->cdev.ops = &mtd_ops;
mtd->cdev.size = mtd->size;
@@ -396,6 +565,8 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
else
mtd->cdev.name = asprintf("%s%d", devname, mtd->class_dev.id);
+ INIT_LIST_HEAD(&mtd->partitions);
+
mtd->cdev.priv = mtd;
mtd->cdev.dev = &mtd->class_dev;
mtd->cdev.mtd = mtd;
@@ -407,19 +578,50 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
dev_add_param_int_ro(&mtd->class_dev, "oobsize", mtd->oobsize, "%u");
}
- devfs_create(&mtd->cdev);
+ ret = devfs_create(&mtd->cdev);
+ if (ret)
+ goto err;
+
+ if (mtd->master && !(mtd->cdev.flags & DEVFS_PARTITION_FIXED)) {
+ struct mtd_info *mtdpart;
+
+ list_for_each_entry(mtdpart, &mtd->master->partitions, partitions_entry) {
+ if (mtdpart->master_offset + mtdpart->size <= mtd->master_offset)
+ continue;
+ if (mtd->master_offset + mtd->size <= mtdpart->master_offset)
+ continue;
+ dev_err(&mtd->class_dev, "New partition %s conflicts with %s\n",
+ mtd->name, mtdpart->name);
+ goto err1;
+ }
+
+ list_add_sort(&mtd->partitions_entry, &mtd->master->partitions, mtd_part_compare);
+ }
if (mtd_can_have_bb(mtd))
mtd->cdev_bb = mtd_add_bb(mtd, NULL);
- if (mtd->parent && !mtd->master)
+ if (mtd->parent && !mtd->master) {
+ dev_add_param(&mtd->class_dev, "partitions", mtd_partition_set, mtd_partition_get, 0);
of_parse_partitions(&mtd->cdev, mtd->parent->device_node);
+ if (IS_ENABLED(CONFIG_OFDEVICE) && mtd->parent->device_node) {
+ mtd->of_path = xstrdup(mtd->parent->device_node->full_name);
+ of_register_fixup(of_mtd_fixup, mtd);
+ }
+ }
list_for_each_entry(hook, &mtd_register_hooks, hook)
if (hook->add_mtd_device)
hook->add_mtd_device(mtd, devname, &hook->priv);
return 0;
+err1:
+ devfs_remove(&mtd->cdev);
+err:
+ free(mtd->cdev.name);
+ unregister_device(&mtd->class_dev);
+
+ return ret;
}
int del_mtd_device (struct mtd_info *mtd)
@@ -431,9 +633,14 @@ int del_mtd_device (struct mtd_info *mtd)
hook->del_mtd_device(mtd, &hook->priv);
devfs_remove(&mtd->cdev);
+ if (mtd->cdev_bb)
+ mtd_del_bb(mtd);
unregister_device(&mtd->class_dev);
free(mtd->param_size.value);
free(mtd->cdev.name);
+ if (mtd->master)
+ list_del(&mtd->partitions_entry);
+
return 0;
}
diff --git a/drivers/mtd/nand/nand-bb.c b/drivers/mtd/nand/nand-bb.c
index 23957c358e..539c685482 100644
--- a/drivers/mtd/nand/nand-bb.c
+++ b/drivers/mtd/nand/nand-bb.c
@@ -307,6 +307,17 @@ err:
return ERR_PTR(ret);
}
+void mtd_del_bb(struct mtd_info *mtd)
+{
+ struct cdev *cdev = mtd->cdev_bb;
+ struct nand_bb *bb = container_of(cdev, struct nand_bb, cdev);
+
+ devfs_remove(&bb->cdev);
+ list_del_init(&bb->list);
+ free(bb->name);
+ free(bb);
+}
+
/**
* Add a bad block aware device ontop of another (NAND) device
* @param[in] dev The device to add a partition on
@@ -335,10 +346,7 @@ int dev_remove_bb_dev(const char *name)
list_for_each_entry_safe(bb, tmp, &bb_list, list) {
if (!strcmp(bb->cdev.name, name)) {
- devfs_remove(&bb->cdev);
- list_del_init(&bb->list);
- free(bb->name);
- free(bb);
+ mtd_del_bb(bb->mtd);
return 0;
}
}
diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c
index e393fa68f8..98b90819bd 100644
--- a/drivers/mtd/partition.c
+++ b/drivers/mtd/partition.c
@@ -108,6 +108,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
uint64_t size, unsigned long flags, const char *name)
{
struct mtd_info *part;
+ int ret;
part = xzalloc(sizeof(*part));
@@ -122,6 +123,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
part->ecclayout = mtd->ecclayout;
part->ecc_strength = mtd->ecc_strength;
part->subpage_sft = mtd->subpage_sft;
+ part->cdev.flags = flags;
if (mtd->numeraseregions > 1) {
/* Deal with variable erase size stuff */
@@ -160,7 +162,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
part->block_isbad = mtd->block_isbad ? mtd_part_block_isbad : NULL;
part->size = size;
- part->name = strdup(name);
+ part->name = xstrdup(name);
part->master_offset = offset;
part->master = mtd;
@@ -168,9 +170,17 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
if (!strncmp(mtd->cdev.name, name, strlen(mtd->cdev.name)))
part->cdev.partname = xstrdup(name + strlen(mtd->cdev.name) + 1);
- add_mtd_device(part, part->name, DEVICE_ID_SINGLE);
+ ret = add_mtd_device(part, part->name, DEVICE_ID_SINGLE);
+ if (ret)
+ goto err;
return part;
+err:
+ free(part->cdev.partname);
+ free(part->name);
+ free(part);
+
+ return ERR_PTR(ret);
}
int mtd_del_partition(struct mtd_info *part)