| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Some devices made by H3C use a "VFS" filesystem to store firmware images.
- * This parses the start of the filesystem to read the length of the first
- * file (the kernel image). It then searches for the rootfs after the end of
- * the file data. This driver assumes that the filesystem was generated by
- * mkh3cvfs, and only works if the filesystem matches the expected layout,
- * which includes the file name of the kernel image.
- */
- #include <linux/module.h>
- #include <linux/kernel.h>
- #include <linux/slab.h>
- #include <linux/of.h>
- #include <linux/types.h>
- #include <linux/mtd/mtd.h>
- #include <linux/mtd/partitions.h>
- #include "mtdsplit.h"
- #define VFS_ERASEBLOCK_SIZE 0x10000
- #define VFS_BLOCK_SIZE 0x400
- #define VFS_BLOCKS_PER_ERASEBLOCK (VFS_ERASEBLOCK_SIZE / VFS_BLOCK_SIZE)
- #define FORMAT_FLAG_OFFSET 0x0
- #define FORMAT_FLAG (VFS_ERASEBLOCK_SIZE << 12 | VFS_BLOCK_SIZE)
- #define FILE_ENTRY_OFFSET 0x800
- #define FILE_ENTRY_FLAGS 0x3f
- #define FILE_ENTRY_PARENT_BLOCK 0
- #define FILE_ENTRY_PARENT_INDEX 0
- #define FILE_ENTRY_DATA_BLOCK 2
- #define FILE_ENTRY_NAME "openwrt-kernel.bin"
- #define NR_PARTS 2
- struct file_entry {
- uint8_t flags;
- uint8_t res0[5];
- uint16_t year;
- uint8_t month;
- uint8_t day;
- uint8_t hour;
- uint8_t minute;
- uint8_t second;
- uint8_t res1[3];
- uint32_t length;
- uint32_t parent_block;
- uint16_t parent_index;
- uint8_t res2[2];
- uint32_t data_block;
- char name[96];
- } __attribute__ ((packed));
- static inline size_t block_offset(int block)
- {
- return VFS_ERASEBLOCK_SIZE * (block / (VFS_BLOCKS_PER_ERASEBLOCK-1))
- + VFS_BLOCK_SIZE * (1 + (block % (VFS_BLOCKS_PER_ERASEBLOCK-1)));
- }
- static inline int block_count(size_t size)
- {
- return (size + VFS_BLOCK_SIZE - 1) / VFS_BLOCK_SIZE;
- }
- static int mtdsplit_h3c_vfs_parse(struct mtd_info *mtd,
- const struct mtd_partition **pparts,
- struct mtd_part_parser_data *data)
- {
- struct mtd_partition *parts;
- uint32_t format_flag;
- struct file_entry file_entry;
- size_t retlen;
- int err;
- size_t kernel_size;
- size_t expected_offset;
- size_t rootfs_offset;
- if (mtd->erasesize != VFS_ERASEBLOCK_SIZE)
- return -EINVAL;
- /* Check format flag */
- err = mtd_read(mtd, FORMAT_FLAG_OFFSET, sizeof(format_flag), &retlen,
- (void *) &format_flag);
- if (err)
- return err;
- if (retlen != sizeof(format_flag))
- return -EIO;
- if (format_flag != FORMAT_FLAG)
- return -EINVAL;
- /* Check file entry */
- err = mtd_read(mtd, FILE_ENTRY_OFFSET, sizeof(file_entry), &retlen,
- (void *) &file_entry);
- if (err)
- return err;
- if (retlen != sizeof(file_entry))
- return -EIO;
- if (file_entry.flags != FILE_ENTRY_FLAGS)
- return -EINVAL;
- if (file_entry.parent_block != FILE_ENTRY_PARENT_BLOCK)
- return -EINVAL;
- if (file_entry.parent_index != FILE_ENTRY_PARENT_INDEX)
- return -EINVAL;
- if (file_entry.data_block != FILE_ENTRY_DATA_BLOCK)
- return -EINVAL;
- if (strncmp(file_entry.name, FILE_ENTRY_NAME, sizeof(file_entry.name)) != 0)
- return -EINVAL;
- /* Find rootfs offset */
- kernel_size = block_offset(file_entry.data_block +
- block_count(file_entry.length) - 1) +
- VFS_BLOCK_SIZE;
- expected_offset = mtd_roundup_to_eb(kernel_size, mtd);
- err = mtd_find_rootfs_from(mtd, expected_offset, mtd->size,
- &rootfs_offset, NULL);
- if (err)
- return err;
- parts = kzalloc(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 = mtd->size - rootfs_offset;
- *pparts = parts;
- return NR_PARTS;
- }
- static const struct of_device_id mtdsplit_h3c_vfs_of_match_table[] = {
- { .compatible = "h3c,vfs-firmware" },
- {},
- };
- MODULE_DEVICE_TABLE(of, mtdsplit_h3c_vfs_of_match_table);
- static struct mtd_part_parser mtdsplit_h3c_vfs_parser = {
- .owner = THIS_MODULE,
- .name = "h3c-vfs",
- .of_match_table = mtdsplit_h3c_vfs_of_match_table,
- .parse_fn = mtdsplit_h3c_vfs_parse,
- .type = MTD_PARSER_TYPE_FIRMWARE,
- };
- module_mtd_part_parser(mtdsplit_h3c_vfs_parser);
|