/* * MTD splitter for MikroTik NOR devices * * Copyright (C) 2017 Thibaut VARENE * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation. * * The rootfs is expected at erase-block boundary due to the use of * mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header * for two main reasons: * - the original header uses weakly defined types (int, enum...) which can * vary in length depending on build host (and the struct is not packed), * and the name field can have a different total length depending on * whether or not the yaffs code was _built_ with unicode support. * - the only field that could be of real use here (file_size_low) contains * invalid data in the header generated by kernel2minor, so we cannot use * it to infer the exact position of the rootfs and do away with * mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs). */ #include #include #include #include #include #include #include #include #include "mtdsplit.h" #define YAFFS_OBJECT_TYPE_FILE 0x1 #define YAFFS_OBJECTID_ROOT 0x1 #define YAFFS_SUM_UNUSED 0xFFFF #define YAFFS_MAX_NAME_LENGTH 127 #define YAFFS_NAME_KERNEL "kernel" #define YAFFS_NAME_BOOTIMAGE "bootimage" #define MINOR_NR_PARTS 2 /* * This structure is based on yaffs_obj_hdr from yaffs_guts.h * The weak types match upstream. The fields have cpu-endianness */ struct minor_header { int yaffs_type; int yaffs_obj_id; u16 yaffs_sum_unused; char yaffs_name[YAFFS_MAX_NAME_LENGTH]; }; static int mtdsplit_parse_minor(struct mtd_info *master, const struct mtd_partition **pparts, struct mtd_part_parser_data *data) { struct minor_header hdr; size_t hdr_len, retlen; size_t rootfs_offset; struct mtd_partition *parts; int err; hdr_len = sizeof(hdr); err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); if (err) { pr_err("MiNOR mtd_read error: %d\n", err); return err; } if (retlen != hdr_len) { pr_err("MiNOR mtd_read too short\n"); return -EIO; } /* match header */ if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) { pr_info("MiNOR YAFFS first type not matched\n"); return 0; } if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) { pr_info("MiNOR YAFFS first objectid not matched\n"); return 0; } if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) { pr_info("MiNOR YAFFS first sum not matched\n"); return 0; } if ((memcmp(hdr.yaffs_name, YAFFS_NAME_KERNEL, sizeof(YAFFS_NAME_KERNEL))) && (memcmp(hdr.yaffs_name, YAFFS_NAME_BOOTIMAGE, sizeof(YAFFS_NAME_BOOTIMAGE)))) { pr_info("MiNOR YAFFS first name not matched\n"); return 0; } err = mtd_find_rootfs_from(master, master->erasesize, master->size, &rootfs_offset, NULL); if (err) { pr_info("MiNOR mtd_find_rootfs_from error: %d\n", err); return 0; } parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL); if (!parts) return -ENOMEM; parts[0].name = KERNEL_PART_NAME; parts[0].offset = 0; parts[0].size = rootfs_offset; parts[1].name = ROOTFS_PART_NAME; parts[1].offset = rootfs_offset; parts[1].size = master->size - rootfs_offset; *pparts = parts; return MINOR_NR_PARTS; } static const struct of_device_id mtdsplit_minor_of_match_table[] = { { .compatible = "mikrotik,minor" }, {}, }; MODULE_DEVICE_TABLE(of, mtdsplit_minor_of_match_table); static struct mtd_part_parser mtdsplit_minor_parser = { .owner = THIS_MODULE, .name = "minor-fw", .of_match_table = mtdsplit_minor_of_match_table, .parse_fn = mtdsplit_parse_minor, .type = MTD_PARSER_TYPE_FIRMWARE, }; static int __init mtdsplit_minor_init(void) { register_mtd_parser(&mtdsplit_minor_parser); return 0; } subsys_initcall(mtdsplit_minor_init);