From 39717277d5c87bdb183cf2f258957b44ba99b4df Mon Sep 17 00:00:00 2001
From: OpenWrt community <openwrt-devel@lists.openwrt.org>
Date: Wed, 13 Jul 2022 11:47:35 +0200
Subject: [PATCH] mtd: mtdsplit support

---
 drivers/mtd/Kconfig            |  19 ++++
 drivers/mtd/Makefile           |   2 +
 drivers/mtd/mtdpart.c          | 169 ++++++++++++++++++++++++++++-----
 include/linux/mtd/mtd.h        |  25 +++++
 include/linux/mtd/partitions.h |   7 ++
 5 files changed, 197 insertions(+), 25 deletions(-)

--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -12,6 +12,25 @@ menuconfig MTD
 
 if MTD
 
+menu "OpenWrt specific MTD options"
+
+config MTD_ROOTFS_ROOT_DEV
+	bool "Automatically set 'rootfs' partition to be root filesystem"
+	default y
+
+config MTD_SPLIT_FIRMWARE
+	bool "Automatically split firmware partition for kernel+rootfs"
+	default y
+
+config MTD_SPLIT_FIRMWARE_NAME
+	string "Firmware partition name"
+	depends on MTD_SPLIT_FIRMWARE
+	default "firmware"
+
+source "drivers/mtd/mtdsplit/Kconfig"
+
+endmenu
+
 config MTD_TESTS
 	tristate "MTD tests support (DANGEROUS)"
 	depends on m
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -9,6 +9,8 @@ mtd-y				:= mtdcore.o mtdsuper.o mtdconc
 
 obj-y				+= parsers/
 
+obj-$(CONFIG_MTD_SPLIT)		+= mtdsplit/
+
 # 'Users' - code which presents functionality to userspace.
 obj-$(CONFIG_MTD_BLKDEVS)	+= mtd_blkdevs.o
 obj-$(CONFIG_MTD_BLOCK)		+= mtdblock.o
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -15,11 +15,13 @@
 #include <linux/kmod.h>
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/partitions.h>
+#include <linux/magic.h>
 #include <linux/err.h>
 #include <linux/of.h>
 #include <linux/of_platform.h>
 
 #include "mtdcore.h"
+#include "mtdsplit/mtdsplit.h"
 
 /*
  * MTD methods which simply translate the effective address and pass through
@@ -236,6 +238,147 @@ static int mtd_add_partition_attrs(struc
 	return ret;
 }
 
+static DEFINE_SPINLOCK(part_parser_lock);
+static LIST_HEAD(part_parsers);
+
+static struct mtd_part_parser *mtd_part_parser_get(const char *name)
+{
+	struct mtd_part_parser *p, *ret = NULL;
+
+	spin_lock(&part_parser_lock);
+
+	list_for_each_entry(p, &part_parsers, list)
+		if (!strcmp(p->name, name) && try_module_get(p->owner)) {
+			ret = p;
+			break;
+		}
+
+	spin_unlock(&part_parser_lock);
+
+	return ret;
+}
+
+static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
+{
+	module_put(p->owner);
+}
+
+static struct mtd_part_parser *
+get_partition_parser_by_type(enum mtd_parser_type type,
+			     struct mtd_part_parser *start)
+{
+	struct mtd_part_parser *p, *ret = NULL;
+
+	spin_lock(&part_parser_lock);
+
+	p = list_prepare_entry(start, &part_parsers, list);
+	if (start)
+		mtd_part_parser_put(start);
+
+	list_for_each_entry_continue(p, &part_parsers, list) {
+		if (p->type == type && try_module_get(p->owner)) {
+			ret = p;
+			break;
+		}
+	}
+
+	spin_unlock(&part_parser_lock);
+
+	return ret;
+}
+
+static int parse_mtd_partitions_by_type(struct mtd_info *master,
+					enum mtd_parser_type type,
+					const struct mtd_partition **pparts,
+					struct mtd_part_parser_data *data)
+{
+	struct mtd_part_parser *prev = NULL;
+	int ret = 0;
+
+	while (1) {
+		struct mtd_part_parser *parser;
+
+		parser = get_partition_parser_by_type(type, prev);
+		if (!parser)
+			break;
+
+		ret = (*parser->parse_fn)(master, pparts, data);
+
+		if (ret > 0) {
+			mtd_part_parser_put(parser);
+			printk(KERN_NOTICE
+			       "%d %s partitions found on MTD device %s\n",
+			       ret, parser->name, master->name);
+			break;
+		}
+
+		prev = parser;
+	}
+
+	return ret;
+}
+
+static int
+run_parsers_by_type(struct mtd_info *child, enum mtd_parser_type type)
+{
+	struct mtd_partition *parts;
+	int nr_parts;
+	int i;
+
+	nr_parts = parse_mtd_partitions_by_type(child, type, (const struct mtd_partition **)&parts,
+						NULL);
+	if (nr_parts <= 0)
+		return nr_parts;
+
+	if (WARN_ON(!parts))
+		return 0;
+
+	for (i = 0; i < nr_parts; i++) {
+		/* adjust partition offsets */
+		parts[i].offset += child->part.offset;
+
+		mtd_add_partition(child->parent,
+				  parts[i].name,
+				  parts[i].offset,
+				  parts[i].size);
+	}
+
+	kfree(parts);
+
+	return nr_parts;
+}
+
+#ifdef CONFIG_MTD_SPLIT_FIRMWARE_NAME
+#define SPLIT_FIRMWARE_NAME	CONFIG_MTD_SPLIT_FIRMWARE_NAME
+#else
+#define SPLIT_FIRMWARE_NAME	"unused"
+#endif
+
+static void split_firmware(struct mtd_info *master, struct mtd_info *part)
+{
+	run_parsers_by_type(part, MTD_PARSER_TYPE_FIRMWARE);
+}
+
+static void mtd_partition_split(struct mtd_info *master, struct mtd_info *part)
+{
+	static int rootfs_found = 0;
+
+	if (rootfs_found)
+		return;
+
+	if (of_find_property(mtd_get_of_node(part), "linux,rootfs", NULL) ||
+	    !strcmp(part->name, "rootfs")) {
+		run_parsers_by_type(part, MTD_PARSER_TYPE_ROOTFS);
+
+		rootfs_found = 1;
+	}
+
+	if (IS_ENABLED(CONFIG_MTD_SPLIT_FIRMWARE) &&
+	    !strcmp(part->name, SPLIT_FIRMWARE_NAME) &&
+	    !of_find_property(mtd_get_of_node(part), "compatible", NULL))
+		split_firmware(master, part);
+}
+
 int mtd_add_partition(struct mtd_info *parent, const char *name,
 		      long long offset, long long length)
 {
@@ -274,6 +417,7 @@ int mtd_add_partition(struct mtd_info *p
 	if (ret)
 		goto err_remove_part;
 
+	mtd_partition_split(parent, child);
 	mtd_add_partition_attrs(child);
 
 	return 0;
@@ -422,6 +566,7 @@ int add_mtd_partitions(struct mtd_info *
 			goto err_del_partitions;
 		}
 
+		mtd_partition_split(master, child);
 		mtd_add_partition_attrs(child);
 
 		/* Look for subpartitions */
@@ -438,31 +583,6 @@ err_del_partitions:
 	return ret;
 }
 
-static DEFINE_SPINLOCK(part_parser_lock);
-static LIST_HEAD(part_parsers);
-
-static struct mtd_part_parser *mtd_part_parser_get(const char *name)
-{
-	struct mtd_part_parser *p, *ret = NULL;
-
-	spin_lock(&part_parser_lock);
-
-	list_for_each_entry(p, &part_parsers, list)
-		if (!strcmp(p->name, name) && try_module_get(p->owner)) {
-			ret = p;
-			break;
-		}
-
-	spin_unlock(&part_parser_lock);
-
-	return ret;
-}
-
-static inline void mtd_part_parser_put(const struct mtd_part_parser *p)
-{
-	module_put(p->owner);
-}
-
 /*
  * Many partition parsers just expected the core to kfree() all their data in
  * one chunk. Do that by default.
--- a/include/linux/mtd/mtd.h
+++ b/include/linux/mtd/mtd.h
@@ -615,6 +615,24 @@ static inline void mtd_align_erase_req(s
 		req->len += mtd->erasesize - mod;
 }
 
+static inline uint64_t mtd_roundup_to_eb(uint64_t sz, struct mtd_info *mtd)
+{
+	if (mtd_mod_by_eb(sz, mtd) == 0)
+		return sz;
+
+	/* Round up to next erase block */
+	return (mtd_div_by_eb(sz, mtd) + 1) * mtd->erasesize;
+}
+
+static inline uint64_t mtd_rounddown_to_eb(uint64_t sz, struct mtd_info *mtd)
+{
+	if (mtd_mod_by_eb(sz, mtd) == 0)
+		return sz;
+
+	/* Round down to the start of the current erase block */
+	return (mtd_div_by_eb(sz, mtd)) * mtd->erasesize;
+}
+
 static inline uint32_t mtd_div_by_ws(uint64_t sz, struct mtd_info *mtd)
 {
 	if (mtd->writesize_shift)
@@ -688,6 +706,13 @@ extern struct mtd_info *of_get_mtd_devic
 extern struct mtd_info *get_mtd_device_nm(const char *name);
 extern void put_mtd_device(struct mtd_info *mtd);
 
+static inline uint64_t mtdpart_get_offset(const struct mtd_info *mtd)
+{
+	if (!mtd_is_partition(mtd))
+		return 0;
+
+	return mtd->part.offset;
+}
 
 struct mtd_notifier {
 	void (*add)(struct mtd_info *mtd);
--- a/include/linux/mtd/partitions.h
+++ b/include/linux/mtd/partitions.h
@@ -75,6 +75,12 @@ struct mtd_part_parser_data {
  * Functions dealing with the various ways of partitioning the space
  */
 
+enum mtd_parser_type {
+	MTD_PARSER_TYPE_DEVICE = 0,
+	MTD_PARSER_TYPE_ROOTFS,
+	MTD_PARSER_TYPE_FIRMWARE,
+};
+
 struct mtd_part_parser {
 	struct list_head list;
 	struct module *owner;
@@ -83,6 +89,7 @@ struct mtd_part_parser {
 	int (*parse_fn)(struct mtd_info *, const struct mtd_partition **,
 			struct mtd_part_parser_data *);
 	void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
+	enum mtd_parser_type type;
 };
 
 /* Container for passing around a set of parsed partitions */
