--- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -12,6 +12,25 @@ menuconfig MTD if MTD +menu "OpenWrt specific MTD options" + +config MTD_ROOTFS_ROOT_DEV + bool "Automatically set 'rootfs' partition to be root filesystem" + default y + +config MTD_SPLIT_FIRMWARE + bool "Automatically split firmware partition for kernel+rootfs" + default y + +config MTD_SPLIT_FIRMWARE_NAME + string "Firmware partition name" + depends on MTD_SPLIT_FIRMWARE + default "firmware" + +source "drivers/mtd/mtdsplit/Kconfig" + +endmenu + config MTD_TESTS tristate "MTD tests support (DANGEROUS)" depends on m --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -15,10 +15,12 @@ #include #include #include +#include #include #include #include "mtdcore.h" +#include "mtdsplit/mtdsplit.h" /* * MTD methods which simply translate the effective address and pass through @@ -236,6 +238,146 @@ static int mtd_add_partition_attrs(struc return ret; } +static DEFINE_SPINLOCK(part_parser_lock); +static LIST_HEAD(part_parsers); + +static struct mtd_part_parser *mtd_part_parser_get(const char *name) +{ + struct mtd_part_parser *p, *ret = NULL; + + spin_lock(&part_parser_lock); + + list_for_each_entry(p, &part_parsers, list) + if (!strcmp(p->name, name) && try_module_get(p->owner)) { + ret = p; + break; + } + + spin_unlock(&part_parser_lock); + + return ret; +} + +static inline void mtd_part_parser_put(const struct mtd_part_parser *p) +{ + module_put(p->owner); +} + +static struct mtd_part_parser * +get_partition_parser_by_type(enum mtd_parser_type type, + struct mtd_part_parser *start) +{ + struct mtd_part_parser *p, *ret = NULL; + + spin_lock(&part_parser_lock); + + p = list_prepare_entry(start, &part_parsers, list); + if (start) + mtd_part_parser_put(start); + + list_for_each_entry_continue(p, &part_parsers, list) { + if (p->type == type && try_module_get(p->owner)) { + ret = p; + break; + } + } + + spin_unlock(&part_parser_lock); + + return ret; +} + +static int parse_mtd_partitions_by_type(struct mtd_info *master, + enum mtd_parser_type type, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_part_parser *prev = NULL; + int ret = 0; + + while (1) { + struct mtd_part_parser *parser; + + parser = get_partition_parser_by_type(type, prev); + if (!parser) + break; + + ret = (*parser->parse_fn)(master, pparts, data); + + if (ret > 0) { + mtd_part_parser_put(parser); + printk(KERN_NOTICE + "%d %s partitions found on MTD device %s\n", + ret, parser->name, master->name); + break; + } + + prev = parser; + } + + return ret; +} + +static int +run_parsers_by_type(struct mtd_info *child, enum mtd_parser_type type) +{ + struct mtd_partition *parts; + int nr_parts; + int i; + + nr_parts = parse_mtd_partitions_by_type(child, type, (const struct mtd_partition **)&parts, + NULL); + if (nr_parts <= 0) + return nr_parts; + + if (WARN_ON(!parts)) + return 0; + + for (i = 0; i < nr_parts; i++) { + /* adjust partition offsets */ + parts[i].offset += child->part.offset; + + mtd_add_partition(child->parent, + parts[i].name, + parts[i].offset, + parts[i].size); + } + + kfree(parts); + + return nr_parts; +} + +#ifdef CONFIG_MTD_SPLIT_FIRMWARE_NAME +#define SPLIT_FIRMWARE_NAME CONFIG_MTD_SPLIT_FIRMWARE_NAME +#else +#define SPLIT_FIRMWARE_NAME "unused" +#endif + +static void split_firmware(struct mtd_info *master, struct mtd_info *part) +{ + run_parsers_by_type(part, MTD_PARSER_TYPE_FIRMWARE); +} + +static void mtd_partition_split(struct mtd_info *master, struct mtd_info *part) +{ + static int rootfs_found = 0; + + if (rootfs_found) + return; + + if (!strcmp(part->name, "rootfs")) { + run_parsers_by_type(part, MTD_PARSER_TYPE_ROOTFS); + + rootfs_found = 1; + } + + if (IS_ENABLED(CONFIG_MTD_SPLIT_FIRMWARE) && + !strcmp(part->name, SPLIT_FIRMWARE_NAME) && + !of_find_property(mtd_get_of_node(part), "compatible", NULL)) + split_firmware(master, part); +} + int mtd_add_partition(struct mtd_info *parent, const char *name, long long offset, long long length) { @@ -274,6 +416,7 @@ int mtd_add_partition(struct mtd_info *p if (ret) goto err_remove_part; + mtd_partition_split(parent, child); mtd_add_partition_attrs(child); return 0; @@ -422,6 +565,7 @@ int add_mtd_partitions(struct mtd_info * goto err_del_partitions; } + mtd_partition_split(master, child); mtd_add_partition_attrs(child); /* Look for subpartitions */ @@ -438,31 +582,6 @@ err_del_partitions: return ret; } -static DEFINE_SPINLOCK(part_parser_lock); -static LIST_HEAD(part_parsers); - -static struct mtd_part_parser *mtd_part_parser_get(const char *name) -{ - struct mtd_part_parser *p, *ret = NULL; - - spin_lock(&part_parser_lock); - - list_for_each_entry(p, &part_parsers, list) - if (!strcmp(p->name, name) && try_module_get(p->owner)) { - ret = p; - break; - } - - spin_unlock(&part_parser_lock); - - return ret; -} - -static inline void mtd_part_parser_put(const struct mtd_part_parser *p) -{ - module_put(p->owner); -} - /* * Many partition parsers just expected the core to kfree() all their data in * one chunk. Do that by default. --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -75,6 +75,12 @@ struct mtd_part_parser_data { * Functions dealing with the various ways of partitioning the space */ +enum mtd_parser_type { + MTD_PARSER_TYPE_DEVICE = 0, + MTD_PARSER_TYPE_ROOTFS, + MTD_PARSER_TYPE_FIRMWARE, +}; + struct mtd_part_parser { struct list_head list; struct module *owner; @@ -83,6 +89,7 @@ struct mtd_part_parser { int (*parse_fn)(struct mtd_info *, const struct mtd_partition **, struct mtd_part_parser_data *); void (*cleanup)(const struct mtd_partition *pparts, int nr_parts); + enum mtd_parser_type type; }; /* Container for passing around a set of parsed partitions */ --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -9,6 +9,8 @@ mtd-y := mtdcore.o mtdsuper.o mtdconc obj-y += parsers/ +obj-$(CONFIG_MTD_SPLIT) += mtdsplit/ + # 'Users' - code which presents functionality to userspace. obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o obj-$(CONFIG_MTD_BLOCK) += mtdblock.o --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -608,6 +608,24 @@ static inline void mtd_align_erase_req(s req->len += mtd->erasesize - mod; } +static inline uint64_t mtd_roundup_to_eb(uint64_t sz, struct mtd_info *mtd) +{ + if (mtd_mod_by_eb(sz, mtd) == 0) + return sz; + + /* Round up to next erase block */ + return (mtd_div_by_eb(sz, mtd) + 1) * mtd->erasesize; +} + +static inline uint64_t mtd_rounddown_to_eb(uint64_t sz, struct mtd_info *mtd) +{ + if (mtd_mod_by_eb(sz, mtd) == 0) + return sz; + + /* Round down to the start of the current erase block */ + return (mtd_div_by_eb(sz, mtd)) * mtd->erasesize; +} + static inline uint32_t mtd_div_by_ws(uint64_t sz, struct mtd_info *mtd) { if (mtd->writesize_shift) @@ -680,6 +698,13 @@ extern void __put_mtd_device(struct mtd_ extern struct mtd_info *get_mtd_device_nm(const char *name); extern void put_mtd_device(struct mtd_info *mtd); +static inline uint64_t mtdpart_get_offset(const struct mtd_info *mtd) +{ + if (!mtd_is_partition(mtd)) + return 0; + + return mtd->part.offset; +} struct mtd_notifier { void (*add)(struct mtd_info *mtd);