--- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -12,6 +12,32 @@ 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_ROOTFS_SPLIT + bool "Automatically split 'rootfs' partition for squashfs" + 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" + +config MTD_UIMAGE_SPLIT + bool "Enable split support for firmware partitions containing a uImage" + depends on MTD_SPLIT_FIRMWARE + default y + +endmenu + config MTD_TESTS tristate "MTD tests support (DANGEROUS)" depends on m --- a/drivers/mtd/mtdpart.c +++ b/drivers/mtd/mtdpart.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "mtdcore.h" @@ -45,13 +46,14 @@ struct mtd_part { struct list_head list; }; +static void mtd_partition_split(struct mtd_info *master, struct mtd_part *part); + /* * Given a pointer to the MTD object in the mtd_part structure, we can retrieve * the pointer to that structure with this macro. */ #define PART(x) ((struct mtd_part *)(x)) - /* * MTD methods which simply translate the effective address and pass through * to the _real_ device. @@ -533,8 +535,10 @@ out_register: return slave; } -int mtd_add_partition(struct mtd_info *master, char *name, - long long offset, long long length) + +static int +__mtd_add_partition(struct mtd_info *master, char *name, + long long offset, long long length, bool dup_check) { struct mtd_partition part; struct mtd_part *p, *new; @@ -566,21 +570,24 @@ int mtd_add_partition(struct mtd_info *m end = offset + length; mutex_lock(&mtd_partitions_mutex); - list_for_each_entry(p, &mtd_partitions, list) - if (p->master == master) { - if ((start >= p->offset) && - (start < (p->offset + p->mtd.size))) - goto err_inv; - - if ((end >= p->offset) && - (end < (p->offset + p->mtd.size))) - goto err_inv; - } + if (dup_check) { + list_for_each_entry(p, &mtd_partitions, list) + if (p->master == master) { + if ((start >= p->offset) && + (start < (p->offset + p->mtd.size))) + goto err_inv; + + if ((end >= p->offset) && + (end < (p->offset + p->mtd.size))) + goto err_inv; + } + } list_add(&new->list, &mtd_partitions); mutex_unlock(&mtd_partitions_mutex); add_mtd_device(&new->mtd); + mtd_partition_split(master, new); return ret; err_inv: @@ -590,6 +597,12 @@ err_inv: } EXPORT_SYMBOL_GPL(mtd_add_partition); +int mtd_add_partition(struct mtd_info *master, char *name, + long long offset, long long length) +{ + return __mtd_add_partition(master, name, offset, length, true); +} + int mtd_del_partition(struct mtd_info *master, int partno) { struct mtd_part *slave, *next; @@ -613,6 +626,144 @@ int mtd_del_partition(struct mtd_info *m } EXPORT_SYMBOL_GPL(mtd_del_partition); +static inline unsigned long +mtd_pad_erasesize(struct mtd_info *mtd, int offset, int len) +{ + unsigned long mask = mtd->erasesize - 1; + + len += offset & mask; + len = (len + mask) & ~mask; + len -= offset & mask; + return len; +} + +#define ROOTFS_SPLIT_NAME "rootfs_data" + +struct squashfs_super_block { + __le32 s_magic; + __le32 pad0[9]; + __le64 bytes_used; +}; + + +static int split_squashfs(struct mtd_info *master, int offset, int *split_offset) +{ + struct squashfs_super_block sb; + int len, ret; + + ret = mtd_read(master, offset, sizeof(sb), &len, (void *) &sb); + if (ret || (len != sizeof(sb))) { + printk(KERN_ALERT "split_squashfs: error occured while reading " + "from \"%s\"\n", master->name); + return -EINVAL; + } + + if (SQUASHFS_MAGIC != le32_to_cpu(sb.s_magic) ) { + printk(KERN_ALERT "split_squashfs: no squashfs found in \"%s\"\n", + master->name); + *split_offset = 0; + return 0; + } + + if (le64_to_cpu((sb.bytes_used)) <= 0) { + printk(KERN_ALERT "split_squashfs: squashfs is empty in \"%s\"\n", + master->name); + *split_offset = 0; + return 0; + } + + len = (u32) le64_to_cpu(sb.bytes_used); + len = mtd_pad_erasesize(master, offset, len); + *split_offset = offset + len; + + return 0; +} + +static void split_rootfs_data(struct mtd_info *master, struct mtd_part *part) +{ + unsigned int split_offset = 0; + unsigned int split_size; + int ret; + + ret = split_squashfs(master, part->offset, &split_offset); + if (ret) + return; + + if (split_offset <= 0) + return; + + split_size = part->mtd.size - (split_offset - part->offset); + printk(KERN_INFO "mtd: partition \"%s\" created automatically, ofs=0x%x, len=0x%x\n", + ROOTFS_SPLIT_NAME, split_offset, split_size); + + __mtd_add_partition(master, ROOTFS_SPLIT_NAME, split_offset, + split_size, false); +} + +#define UBOOT_MAGIC 0x27051956 + +static void split_uimage(struct mtd_info *master, struct mtd_part *part) +{ + struct { + __be32 magic; + __be32 pad[2]; + __be32 size; + } hdr; + size_t len; + + if (mtd_read(master, part->offset, sizeof(hdr), &len, (void *) &hdr)) + return; + + if (len != sizeof(hdr) || hdr.magic != cpu_to_be32(UBOOT_MAGIC)) + return; + + len = be32_to_cpu(hdr.size) + 0x40; + len = mtd_pad_erasesize(master, part->offset, len); + if (len + master->erasesize > part->mtd.size) + return; + + __mtd_add_partition(master, "rootfs", part->offset + len, + part->mtd.size - len, false); +} + +#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_part *part) +{ + if (config_enabled(CONFIG_MTD_UIMAGE_SPLIT)) + split_uimage(master, part); +} + +void __weak arch_split_mtd_part(struct mtd_info *master, const char *name, + int offset, int size) +{ +} + +static void mtd_partition_split(struct mtd_info *master, struct mtd_part *part) +{ + static int rootfs_found = 0; + + if (rootfs_found) + return; + + if (!strcmp(part->mtd.name, "rootfs")) { + rootfs_found = 1; + + if (config_enabled(CONFIG_MTD_ROOTFS_SPLIT)) + split_rootfs_data(master, part); + } + + if (!strcmp(part->mtd.name, SPLIT_FIRMWARE_NAME) && + config_enabled(CONFIG_MTD_SPLIT_FIRMWARE)) + split_firmware(master, part); + + arch_split_mtd_part(master, part->mtd.name, part->offset, + part->mtd.size); +} /* * This function, given a master MTD object and a partition table, creates * and registers slave MTD objects which are bound to the master according to @@ -642,6 +793,7 @@ int add_mtd_partitions(struct mtd_info * mutex_unlock(&mtd_partitions_mutex); add_mtd_device(&slave->mtd); + mtd_partition_split(master, slave); cur_offset = slave->offset + slave->mtd.size; } --- a/include/linux/mtd/partitions.h +++ b/include/linux/mtd/partitions.h @@ -84,5 +84,7 @@ int mtd_add_partition(struct mtd_info *m long long offset, long long length); int mtd_del_partition(struct mtd_info *master, int partno); uint64_t mtd_get_device_size(const struct mtd_info *mtd); +extern void __weak arch_split_mtd_part(struct mtd_info *master, + const char *name, int offset, int size); #endif