Skip to content

Commit

Permalink
kernel: mtdsplit: add support for H3C VFS filesystem
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
janh authored and dangowrt committed Jul 28, 2022
1 parent 81e3017 commit 3b12612
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 0 deletions.
1 change: 1 addition & 0 deletions target/linux/generic/config-5.10
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions target/linux/generic/config-5.15
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
Expand Up @@ -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
1 change: 1 addition & 0 deletions target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
Expand Up @@ -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
170 changes: 170 additions & 0 deletions 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);

0 comments on commit 3b12612

Please sign in to comment.