generic: add mstc-boot mtdsplit parser

Add new mtdsplit parser "mstc-boot" for the devices manufactured by MSTC
(Mitra Star Technology Corp.). This is necessary to handle dual-boot on
those devices.
This parser splits kernel+rootfs or only rootfs(or UBI) based on the
image in the firmware partition or pre-defined partitions in dts, and
"bootnum" value in the "persist" (or "working") partition.

Note: "bootnum" is used for switching active firmware partitions on the
      devices manufactured by MSTC and '1' or '2' are used on most
      devices. But some devices use '0' or '1'. (example: I-O DATA
      WN-DEAX1800GR)

Sequence:

1. obtain "bootnum" value
2. child nodes exsist (regardless of bootnum)
   -> fixed partitions
      (active parts  : without bootnum (ex.: "kernel", "rootfs")
       inactive parts: with bootnum (ex.: "kernel2", "rootfs2"))
3. current partition is active (dt bootnum == mtd bootnum)
   -> image-based partitions

Device Tree:

- common

  - mstc,bootnum  : "bootnum" value for the mtd partition (0/1/2)
  - mstc,persist  : phandle of "persist" partition containing "bootnum"
                    value

- fixed partitions

  - #address-cells: indicate cell count of address of child nodes (1)
  - #size-cells   : indicate cell count of size of child nodes (1)
  - (child nodes) : define the child partitions
    - reg         : define the offset and size
    - label-base  : define the base name of the partition
      - (example) : base:"kernel"->"kernel"(active)/"kernel2"(inactive)

  example:

  partition@3c0000 {
  	compatible = "mstc,boot";
  	reg = <0x3c0000 0x3240000>;
  	label = "firmware1";
  	mstc,bootnum = <1>;
  	mstc,persist = <&mtd_persist>;
  	#address-cells = <1>;
  	#size-cells = <1>;

  	partition@0 {
  		reg = <0x0 0x800000>;
  		label-base = "kernel";
  	};

  	partition@800000 {
  		reg = <0x800000 0x2a40000>;
  		label-base = "ubi";
 	};
  };

- image-based partitions

  (no additional properties)

  example:

  partition@5a0000 {
  	compatible = "mstc,boot";
  	label = "firmware1";
  	reg = <0x5a0000 0x3200000>;
  	mstc,bootnum = <1>;
  	mstc,persist = <&mtd_persist>;
  };

Signed-off-by: INAGAKI Hiroshi <musashino.open@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/18976
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
This commit is contained in:
INAGAKI Hiroshi 2025-04-28 18:44:29 +09:00 committed by Hauke Mehrtens
parent c08c2d6eb3
commit ddf7d63e94
5 changed files with 278 additions and 0 deletions

View file

@ -3915,6 +3915,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware"
# CONFIG_MTD_SPLIT_JIMAGE_FW is not set
# CONFIG_MTD_SPLIT_LZMA_FW is not set
# CONFIG_MTD_SPLIT_MINOR_FW is not set
# CONFIG_MTD_SPLIT_MSTC_BOOT is not set
# CONFIG_MTD_SPLIT_SEAMA_FW is not set
# CONFIG_MTD_SPLIT_SEIL_FW is not set
CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y

View file

@ -3803,6 +3803,7 @@ CONFIG_MTD_SPLIT_FIRMWARE_NAME="firmware"
# CONFIG_MTD_SPLIT_JIMAGE_FW is not set
# CONFIG_MTD_SPLIT_LZMA_FW is not set
# CONFIG_MTD_SPLIT_MINOR_FW is not set
# CONFIG_MTD_SPLIT_MSTC_BOOT is not set
# CONFIG_MTD_SPLIT_SEAMA_FW is not set
# CONFIG_MTD_SPLIT_SEIL_FW is not set
CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y

View file

@ -110,3 +110,8 @@ config MTD_SPLIT_SEIL_FW
bool "IIJ SEIL firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
config MTD_SPLIT_MSTC_BOOT
bool "MSTC bootnum-based parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT

View file

@ -17,3 +17,4 @@ 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
obj-$(CONFIG_MTD_SPLIT_MSTC_BOOT) += mtdsplit_mstc_boot.o

View file

@ -0,0 +1,270 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* a mtdsplit parser using "bootnum" value in the "persist" partition
* for the devices manufactured by MSTC (MitraStar Technology Corp.)
*/
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/types.h>
#include <linux/byteorder/generic.h>
#include <linux/slab.h>
#include <linux/libfdt.h>
#include <linux/of_fdt.h>
#include <dt-bindings/mtd/partitions/uimage.h>
#include "mtdsplit.h"
#define PERSIST_BOOTNUM_OFFSET 0x4
#define NR_PARTS_MAX 2
/*
* Legacy format image header,
* all data in network byte order (aka natural aka bigendian).
*/
struct uimage_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
};
/* check whether the current mtd device is active or not */
static int
mstcboot_is_active(struct mtd_info *mtd, u32 *bootnum_dt)
{
struct device_node *np = mtd_get_of_node(mtd);
struct device_node *persist_np;
size_t retlen;
u32 persist_offset;
u_char bootnum;
int ret;
ret = of_property_read_u32(np, "mstc,bootnum", bootnum_dt);
if (ret)
return ret;
persist_np = of_parse_phandle(np, "mstc,persist", 0);
if (!persist_np)
return -ENODATA;
/* is "persist" under the same node? */
if (persist_np->parent != np->parent) {
of_node_put(persist_np);
return -EINVAL;
}
ret = of_property_read_u32(persist_np, "reg", &persist_offset);
of_node_put(persist_np);
if (ret)
return ret;
ret = mtd_read(mtd->parent, persist_offset + PERSIST_BOOTNUM_OFFSET,
1, &retlen, &bootnum);
if (ret)
return ret;
if (retlen != 1)
return -EIO;
return (bootnum == *bootnum_dt) ? 1 : 0;
}
/*
* mainly for NOR devices that uses raw kernel and squashfs
*
* example:
*
* partition@5a0000 {
* compatible = "mstc,boot";
* label = "firmware1";
* reg = <0x5a0000 0x3200000>;
* mstc,bootnum = <1>;
* mstc,persist = <&mtd_persist>;
* };
*/
static int
mstcboot_parse_image_parts(struct mtd_info *mtd,
const struct mtd_partition **pparts)
{
struct mtd_partition *parts;
size_t retlen, kern_len = 0;
size_t rootfs_offset;
enum mtdsplit_part_type type;
u_char buf[0x40];
int ret, nr_parts = 1, index = 0;
ret = mtd_read(mtd, 0, sizeof(struct uimage_header), &retlen, buf);
if (ret)
return ret;
if (retlen != sizeof(struct uimage_header))
return -EIO;
if (be32_to_cpu(*(u32 *)buf) == OF_DT_HEADER) {
/* Flattened Image Tree (FIT) */
struct fdt_header *fdthdr = (void *)buf;
kern_len = be32_to_cpu(fdthdr->totalsize);
} else if (be32_to_cpu(*(u32 *)buf) == IH_MAGIC) {
/* Legacy uImage */
struct uimage_header *uimghdr = (void *)buf;
kern_len = sizeof(*uimghdr) + be32_to_cpu(uimghdr->ih_size);
}
ret = mtd_find_rootfs_from(mtd, kern_len, mtd->size, &rootfs_offset, &type);
if (ret) {
pr_debug("no rootfs in \"%s\"\n", mtd->name);
return ret;
}
if (kern_len > 0)
nr_parts++;
parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
if (kern_len) {
parts[index].name = KERNEL_PART_NAME;
parts[index].offset = 0;
parts[index++].size = rootfs_offset;
}
parts[index].name = (type == MTDSPLIT_PART_TYPE_UBI)
? UBI_PART_NAME : ROOTFS_PART_NAME;
parts[index].offset = rootfs_offset;
parts[index].size = mtd->size - rootfs_offset;
*pparts = parts;
return nr_parts;
}
/*
* mainly for NAND devices that uses raw-kernel and UBI and needs
* splitted kernel/ubi partitions when sysupgrade
*
* example:
*
* partition@3c0000 {
* compatible = "mstc,boot";
* reg = <0x3c0000 0x3240000>;
* label = "firmware1";
* mstc,bootnum = <1>;
* mstc,persist = <&mtd_persist>;
* #address-cells = <1>;
* #size-cells = <1>;
*
* partition@0 {
* reg = <0x0 0x800000>;
* label-base = "kernel";
* };
*
* partition@800000 {
* reg = <0x800000 0x2a40000>;
* label-base = "ubi";
* };
};
*/
static int
mstcboot_parse_fixed_parts(struct mtd_info *mtd,
const struct mtd_partition **pparts,
int active, u32 bootnum_dt)
{
struct device_node *np = mtd_get_of_node(mtd);
struct device_node *child;
struct mtd_partition *parts;
int ret, nr_parts, index = 0;
nr_parts = of_get_child_count(np);
if (nr_parts > NR_PARTS_MAX) {
pr_err("too many partitions found!\n");
return -EINVAL;
}
parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);
if (!parts)
return -ENOMEM;
for_each_child_of_node(np, child) {
u32 reg[2];
if (of_n_addr_cells(child) != 1 ||
of_n_size_cells(child) != 1)
{
ret = -EINVAL;
break;
}
ret = of_property_read_u32_array(child, "reg", reg, 2);
if (ret)
break;
ret = of_property_read_string(child, "label-base",
&parts[index].name);
if (ret)
break;
if (!active) {
parts[index].name = devm_kasprintf(&mtd->dev, GFP_KERNEL,
"%s%u",
parts[index].name, bootnum_dt);
if (!parts[index].name) {
ret = -ENOMEM;
break;
}
}
parts[index].offset = reg[0];
parts[index].size = reg[1];
index++;
}
of_node_put(child);
if (ret)
kfree(parts);
else
*pparts = parts;
return ret ? ret : nr_parts;
}
static int
mtdsplit_mstcboot_parse(struct mtd_info *mtd,
const struct mtd_partition **pparts,
struct mtd_part_parser_data *data)
{
struct device_node *np = mtd_get_of_node(mtd);
u32 bootnum_dt;
int ret;
ret = mstcboot_is_active(mtd, &bootnum_dt);
if (ret < 0)
goto exit;
if (of_get_child_count(np))
ret = mstcboot_parse_fixed_parts(mtd, pparts, ret, bootnum_dt);
else if (ret != 0)
ret = mstcboot_parse_image_parts(mtd, pparts);
exit:
/*
* return 0 when ret=-ENODEV, to prevent deletion of
* parent mtd partitions on Linux 6.7 and later
*/
return ret == -ENODEV ? 0 : ret;
}
static const struct of_device_id mtdsplit_mstcboot_of_match_table[] = {
{ .compatible = "mstc,boot" },
{},
};
MODULE_DEVICE_TABLE(of, mtdsplit_mstcboot_of_match_table);
static struct mtd_part_parser mtdsplit_mstcboot_parser = {
.owner = THIS_MODULE,
.name = "mstc-boot",
.of_match_table = mtdsplit_mstcboot_of_match_table,
.parse_fn = mtdsplit_mstcboot_parse,
.type = MTD_PARSER_TYPE_FIRMWARE,
};
module_mtd_part_parser(mtdsplit_mstcboot_parser)