diff --git a/target/linux/generic/config-5.10 b/target/linux/generic/config-5.10 index c14851dbdb..2223456fe0 100644 --- a/target/linux/generic/config-5.10 +++ b/target/linux/generic/config-5.10 @@ -3683,6 +3683,7 @@ CONFIG_MTD_SPLIT=y # CONFIG_MTD_SPLIT_FIRMWARE is not set CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" # CONFIG_MTD_SPLIT_FIT_FW is not set +# CONFIG_MTD_SPLIT_H3C_VFS is not set # CONFIG_MTD_SPLIT_JIMAGE_FW is not set # CONFIG_MTD_SPLIT_LZMA_FW is not set # CONFIG_MTD_SPLIT_MINOR_FW is not set diff --git a/target/linux/generic/config-5.15 b/target/linux/generic/config-5.15 index 72a6ee2fbe..c21f7631ec 100644 --- a/target/linux/generic/config-5.15 +++ b/target/linux/generic/config-5.15 @@ -3827,6 +3827,7 @@ CONFIG_MTD_SPLIT=y # CONFIG_MTD_SPLIT_FIRMWARE is not set CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware" # CONFIG_MTD_SPLIT_FIT_FW is not set +# CONFIG_MTD_SPLIT_H3C_VFS is not set # CONFIG_MTD_SPLIT_JIMAGE_FW is not set # CONFIG_MTD_SPLIT_LZMA_FW is not set # CONFIG_MTD_SPLIT_MINOR_FW is not set diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig index 794a39f2c3..f929c6153e 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig @@ -101,3 +101,8 @@ config MTD_SPLIT_ELF_FW bool "ELF loader firmware partition parser" depends on MTD_SPLIT_SUPPORT select MTD_SPLIT + +config MTD_SPLIT_H3C_VFS + bool "Parser finding rootfs appended to H3C VFS" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile index 1461099b7c..a969c336aa 100644 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o obj-$(CONFIG_MTD_SPLIT_ELF_FW) += mtdsplit_elf.o +obj-$(CONFIG_MTD_SPLIT_H3C_VFS) += mtdsplit_h3c_vfs.o diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c new file mode 100644 index 0000000000..f264233dbd --- /dev/null +++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c @@ -0,0 +1,170 @@ +// 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 +#include +#include +#include +#include +#include +#include + +#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);