diff mbox series

[5/7] kernel: mtdsplit: add support for H3C VFS filesystem

Message ID 20220723205319.3326374-6-jan@3e8.eu
State Accepted
Delegated to: Daniel Golle
Headers show
Series realtek: add HPE 1920 support | expand

Commit Message

Jan Hoffmann July 23, 2022, 8:53 p.m. UTC
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              |   1 +
 target/linux/generic/config-5.15              |   1 +
 .../files/drivers/mtd/mtdsplit/Kconfig        |   5 +
 .../files/drivers/mtd/mtdsplit/Makefile       |   1 +
 .../drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c   | 170 ++++++++++++++++++
 5 files changed, 178 insertions(+)
 create mode 100644 target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_h3c_vfs.c
diff mbox series

Patch

diff --git a/target/linux/generic/config-5.10 b/target/linux/generic/config-5.10
index c14851dbdb6f..2223456fe01a 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 72a6ee2fbe0d..c21f7631ec1e 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 794a39f2c322..f929c6153e7f 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 1461099b7c8b..a969c336aaeb 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 000000000000..0adc2b6b1e0b
--- /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);