|
|
@@ -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)
|