diff options
Diffstat (limited to 'drivers/mtd/partition.c')
-rw-r--r-- | drivers/mtd/partition.c | 143 |
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); +} |