aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--target/linux/generic/config-5.101
-rw-r--r--target/linux/generic/config-5.151
-rw-r--r--target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig5
-rw-r--r--target/linux/generic/files/drivers/mtd/mtdsplit/Makefile1
-rw-r--r--target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c170
5 files changed, 178 insertions, 0 deletions
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 <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);