diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index 87ee6f4..299cca1 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_NAND) += nand/ +obj-$(CONFIG_PARTITION_NEED_MTD) += partition.o diff --git a/drivers/mtd/nand/nand.c b/drivers/mtd/nand/nand.c index 4927231..6a150fe 100644 --- a/drivers/mtd/nand/nand.c +++ b/drivers/mtd/nand/nand.c @@ -123,6 +123,7 @@ user->size = info->size; user->erasesize = info->erasesize; user->oobsize = info->oobsize; + user->mtd = info; /* The below fields are obsolete */ user->ecctype = -1; user->eccsize = 0; @@ -220,6 +221,7 @@ mtd->cdev.name = asprintf("nand%d", mtd->class_dev.id); mtd->cdev.priv = mtd; mtd->cdev.dev = &mtd->class_dev; + mtd->cdev.mtd = mtd; sprintf(str, "%u", mtd->size); dev_add_param_fixed(&mtd->class_dev, "size", str); diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c new file mode 100644 index 0000000..df2eb40 --- /dev/null +++ b/drivers/mtd/partition.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include + +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); +} diff --git a/fs/Kconfig b/fs/Kconfig index 3e9de96..d05797a 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -16,4 +16,7 @@ default y prompt "devfs support" +config PARTITION_NEED_MTD + bool + endmenu diff --git a/fs/devfs.c b/fs/devfs.c index 7478ef9..9ce9117 100644 --- a/fs/devfs.c +++ b/fs/devfs.c @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include @@ -170,6 +172,7 @@ static int partition_ioctl(struct cdev *cdev, int request, void *buf) { size_t offset; + struct mtd_info_user *user = buf; switch (request) { case MEMSETBADBLOCK: @@ -178,6 +181,18 @@ offset += cdev->offset; return cdev->ops->ioctl(cdev, request, (void *)offset); case MEMGETINFO: + if (cdev->mtd) { + user->type = cdev->mtd->type; + user->flags = cdev->mtd->flags; + user->size = cdev->mtd->size; + user->erasesize = cdev->mtd->erasesize; + user->oobsize = cdev->mtd->oobsize; + user->mtd = cdev->mtd; + /* The below fields are obsolete */ + user->ecctype = -1; + user->eccsize = 0; + return 0; + } return cdev->ops->ioctl(cdev, request, buf); default: return -EINVAL; @@ -351,6 +366,17 @@ new->dev = cdev->dev; new->flags = flags | DEVFS_IS_PARTITION; +#ifdef CONFIG_PARTITION_NEED_MTD + if (cdev->mtd) { + new->mtd = mtd_add_partition(cdev->mtd, offset, size, flags, name); + if (IS_ERR(new->mtd)) { + int ret = PTR_ERR(new->mtd); + free(new); + return ret; + } + } +#endif + devfs_create(new); return 0; @@ -370,6 +396,11 @@ if (cdev->flags & DEVFS_PARTITION_FIXED) return -EPERM; +#ifdef CONFIG_PARTITION_NEED_MTD + if (cdev->mtd) + mtd_del_partition(cdev->mtd); +#endif + ret = devfs_remove(cdev); if (ret) return ret; diff --git a/include/driver.h b/include/driver.h index 6950c02..ae3e777 100644 --- a/include/driver.h +++ b/include/driver.h @@ -307,6 +307,7 @@ size_t size; unsigned int flags; int open; + struct mtd_info *mtd; }; int devfs_create(struct cdev *); diff --git a/include/linux/mtd/mtd-abi.h b/include/linux/mtd/mtd-abi.h index 04b4227..33e1fe2 100644 --- a/include/linux/mtd/mtd-abi.h +++ b/include/linux/mtd/mtd-abi.h @@ -62,6 +62,7 @@ * (TODO: remove at some point) */ uint32_t ecctype; uint32_t eccsize; + struct mtd_info *mtd; }; struct region_info_user { diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index ca98a16..39ee992 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -229,6 +229,9 @@ struct list_head list; }; +struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, size_t size, + unsigned long flags, const char *name); +void mtd_del_partition(struct mtd_info *mtd); extern void register_mtd_user (struct mtd_notifier *new); extern int unregister_mtd_user (struct mtd_notifier *old);