summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/partition.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/partition.c')
-rw-r--r--drivers/mtd/partition.c143
1 files changed, 143 insertions, 0 deletions
diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c
new file mode 100644
index 0000000000..df2eb40bf8
--- /dev/null
+++ b/drivers/mtd/partition.c
@@ -0,0 +1,143 @@
+#include <common.h>
+#include <errno.h>
+#include <malloc.h>
+#include <linux/err.h>
+#include <linux/mtd/mtd.h>
+
+struct mtd_part {
+ struct mtd_info mtd;
+ struct mtd_info *master;
+ uint64_t offset;
+ struct list_head list;
+};
+
+#define PART(x) ((struct mtd_part *)(x))
+
+static int mtd_part_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+ struct mtd_ecc_stats stats;
+ int res;
+
+ stats = part->master->ecc_stats;
+
+ if (from >= mtd->size)
+ len = 0;
+ else if (from + len > mtd->size)
+ len = mtd->size - from;
+ res = part->master->read(part->master, from + part->offset,
+ len, retlen, buf);
+ return res;
+}
+
+static int mtd_part_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct mtd_part *part = PART(mtd);
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (to >= mtd->size)
+ len = 0;
+ else if (to + len > mtd->size)
+ len = mtd->size - to;
+ return part->master->write(part->master, to + part->offset,
+ len, retlen, buf);
+}
+
+static int mtd_part_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct mtd_part *part = PART(mtd);
+ int ret;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (instr->addr >= mtd->size)
+ return -EINVAL;
+ instr->addr += part->offset;
+ ret = part->master->erase(part->master, instr);
+ if (ret) {
+ if (instr->fail_addr != 0xffffffff)
+ instr->fail_addr -= part->offset;
+ instr->addr -= part->offset;
+ }
+ return ret;
+}
+
+static int mtd_part_block_isbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct mtd_part *part = PART(mtd);
+ if (ofs >= mtd->size)
+ return -EINVAL;
+ ofs += part->offset;
+ return part->master->block_isbad(part->master, ofs);
+}
+
+static int mtd_part_block_markbad(struct mtd_info *mtd, loff_t ofs)
+{
+ struct mtd_part *part = PART(mtd);
+ int res;
+
+ if (!(mtd->flags & MTD_WRITEABLE))
+ return -EROFS;
+ if (ofs >= mtd->size)
+ return -EINVAL;
+ ofs += part->offset;
+ res = part->master->block_markbad(part->master, ofs);
+ if (!res)
+ mtd->ecc_stats.badblocks++;
+ return res;
+}
+
+struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, size_t size,
+ unsigned long flags, const char *name)
+{
+ struct mtd_part *slave;
+ struct mtd_info *slave_mtd;
+ int start = 0, end = 0, i;
+
+ slave = xzalloc(sizeof(*slave));
+ slave_mtd = &slave->mtd;
+
+ memcpy(slave_mtd, mtd, sizeof(*slave));
+
+ /*
+ * find the number of eraseregions the partition includes.
+ * Do not bother to create the mtd_erase_region_infos as
+ * ubi is only interested in its number. UBI does not
+ * yet support multiple erase regions.
+ */
+ for (i = mtd->numeraseregions - 1; i >= 0; i--) {
+ struct mtd_erase_region_info *region = &mtd->eraseregions[i];
+ if (offset >= region->offset &&
+ offset < region->offset + region->erasesize * region->numblocks)
+ start = i;
+ if (offset + size >= region->offset &&
+ offset + size <= region->offset + region->erasesize * region->numblocks)
+ end = i;
+ }
+
+ slave_mtd->numeraseregions = end - start;
+
+ slave_mtd->read = mtd_part_read;
+ slave_mtd->write = mtd_part_write;
+ slave_mtd->erase = mtd_part_erase;
+ slave_mtd->block_isbad = mtd->block_isbad ? mtd_part_block_isbad : NULL;
+ slave_mtd->block_markbad = mtd->block_markbad ? mtd_part_block_markbad : NULL;
+ slave_mtd->size = size;
+ slave_mtd->name = strdup(name);
+
+ slave->offset = offset;
+ slave->master = mtd;
+
+ return slave_mtd;
+}
+
+void mtd_del_partition(struct mtd_info *mtd)
+{
+ struct mtd_part *part = PART(mtd);
+
+ free(mtd->name);
+ free(part);
+}