[openwrt/openwrt] generic: add mstc-boot mtdsplit parser
LEDE Commits
lede-commits at lists.infradead.org
Sun Jul 20 07:14:04 PDT 2025
hauke pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/ddf7d63e946a65d510c4ec0338aea49dc45a976c
commit ddf7d63e946a65d510c4ec0338aea49dc45a976c
Author: INAGAKI Hiroshi <musashino.open at gmail.com>
AuthorDate: Mon Apr 28 18:44:29 2025 +0900
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 at 3c0000 {
compatible = "mstc,boot";
reg = <0x3c0000 0x3240000>;
label = "firmware1";
mstc,bootnum = <1>;
mstc,persist = <&mtd_persist>;
#address-cells = <1>;
#size-cells = <1>;
partition at 0 {
reg = <0x0 0x800000>;
label-base = "kernel";
};
partition at 800000 {
reg = <0x800000 0x2a40000>;
label-base = "ubi";
};
};
- image-based partitions
(no additional properties)
example:
partition at 5a0000 {
compatible = "mstc,boot";
label = "firmware1";
reg = <0x5a0000 0x3200000>;
mstc,bootnum = <1>;
mstc,persist = <&mtd_persist>;
};
Signed-off-by: INAGAKI Hiroshi <musashino.open at gmail.com>
Link: https://github.com/openwrt/openwrt/pull/18976
Signed-off-by: Hauke Mehrtens <hauke at hauke-m.de>
---
target/linux/generic/config-6.12 | 1 +
target/linux/generic/config-6.6 | 1 +
.../generic/files/drivers/mtd/mtdsplit/Kconfig | 5 +
.../generic/files/drivers/mtd/mtdsplit/Makefile | 1 +
.../drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c | 270 +++++++++++++++++++++
5 files changed, 278 insertions(+)
diff --git a/target/linux/generic/config-6.12 b/target/linux/generic/config-6.12
index dbf0d5b3aa..53d799bc19 100644
--- a/target/linux/generic/config-6.12
+++ b/target/linux/generic/config-6.12
@@ -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
diff --git a/target/linux/generic/config-6.6 b/target/linux/generic/config-6.6
index cb1fd5b272..8ede1d244d 100644
--- a/target/linux/generic/config-6.6
+++ b/target/linux/generic/config-6.6
@@ -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
diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
index 17d590890d..396becf160 100644
--- a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
+++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
@@ -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
diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
index e9d63c8332..b85ce5d21f 100644
--- a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
+++ b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
@@ -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
diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c
new file mode 100644
index 0000000000..6d7980792a
--- /dev/null
+++ b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_mstc_boot.c
@@ -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 at 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 at 3c0000 {
+ * compatible = "mstc,boot";
+ * reg = <0x3c0000 0x3240000>;
+ * label = "firmware1";
+ * mstc,bootnum = <1>;
+ * mstc,persist = <&mtd_persist>;
+ * #address-cells = <1>;
+ * #size-cells = <1>;
+ *
+ * partition at 0 {
+ * reg = <0x0 0x800000>;
+ * label-base = "kernel";
+ * };
+ *
+ * partition at 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)
More information about the lede-commits
mailing list