kernel: mtdsplit: add support for H3C VFS filesystem
authorJan Hoffmann <jan@3e8.eu>
Sat, 23 Jul 2022 20:53:17 +0000 (22:53 +0200)
committerDaniel Golle <daniel@makrotopia.org>
Thu, 28 Jul 2022 12:08:56 +0000 (14:08 +0200)
The bootloader on some H3C devices (for example HPE 1920 switches) only
supports booting from flash by reading an image from an "VFS" filesystem
which spans most of the available flash. The filesystem size is hard-
coded in the bootloader. However, as long as no write operations are
performed in the bootloader menu, it is sufficient if the start of the
partition contains a valid filesystem with the kernel image.

This mtdsplit parser reads the size and location of the kernel image and
finds the location of the rootfs stored after it. It assumes that the
filesystem image matches the layout of one generated by mkh3cvfs, with
a filename of "openwrt-kernel.bin" for the kernel image.

Signed-off-by: Jan Hoffmann <jan@3e8.eu>
target/linux/generic/config-5.10
target/linux/generic/config-5.15
target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c [new file with mode: 0644]

index c14851dbdb6f93b39851bb28d996ebcddd4a8109..2223456fe01a0d993fb4762889822de752f8fc41 100644 (file)
@@ -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
index 72a6ee2fbe0d91333626538ed159234ebfa06797..c21f7631ec1ed85b9f216f7ba1222e46b04be703 100644 (file)
@@ -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
index 794a39f2c3221a135d52ba86f57b934b9f5f7ef1..f929c6153e7f603e3ea967be959d60d806052a54 100644 (file)
@@ -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
index 1461099b7c8ba9f53c38f12fa9b88e054f946194..a969c336aaeb983251eb70da7b4c3dbf768f09d7 100644 (file)
@@ -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 (file)
index 0000000..f264233
--- /dev/null
@@ -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);