162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *    drivers/mtd/scpart.c: Sercomm Partition Parser
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *    Copyright (C) 2018 NOGUCHI Hiroshi
662306a36Sopenharmony_ci *    Copyright (C) 2022 Mikhail Zhilkin
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1262306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define	MOD_NAME	"scpart"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#ifdef pr_fmt
1862306a36Sopenharmony_ci#undef pr_fmt
1962306a36Sopenharmony_ci#endif
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define pr_fmt(fmt) MOD_NAME ": " fmt
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define	ID_ALREADY_FOUND	0xffffffffUL
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define	MAP_OFFS_IN_BLK		0x800
2662306a36Sopenharmony_ci#define	MAP_MIRROR_NUM		2
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic const char sc_part_magic[] = {
2962306a36Sopenharmony_ci	'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
3062306a36Sopenharmony_ci};
3162306a36Sopenharmony_ci#define	PART_MAGIC_LEN		sizeof(sc_part_magic)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* assumes that all fields are set by CPU native endian */
3462306a36Sopenharmony_cistruct sc_part_desc {
3562306a36Sopenharmony_ci	uint32_t	part_id;
3662306a36Sopenharmony_ci	uint32_t	part_offs;
3762306a36Sopenharmony_ci	uint32_t	part_bytes;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	return ((pdesc->part_id != 0xffffffffUL) &&
4362306a36Sopenharmony_ci		(pdesc->part_offs != 0xffffffffUL) &&
4462306a36Sopenharmony_ci		(pdesc->part_bytes != 0xffffffffUL));
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
4862306a36Sopenharmony_ci			       struct sc_part_desc **ppdesc)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	int cnt = 0;
5162306a36Sopenharmony_ci	int res = 0;
5262306a36Sopenharmony_ci	int res2;
5362306a36Sopenharmony_ci	uint32_t offs;
5462306a36Sopenharmony_ci	size_t retlen;
5562306a36Sopenharmony_ci	struct sc_part_desc *pdesc = NULL;
5662306a36Sopenharmony_ci	struct sc_part_desc *tmpdesc;
5762306a36Sopenharmony_ci	uint8_t *buf;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	buf = kzalloc(master->erasesize, GFP_KERNEL);
6062306a36Sopenharmony_ci	if (!buf) {
6162306a36Sopenharmony_ci		res = -ENOMEM;
6262306a36Sopenharmony_ci		goto out;
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
6662306a36Sopenharmony_ci	if (res2 || retlen != master->erasesize) {
6762306a36Sopenharmony_ci		res = -EIO;
6862306a36Sopenharmony_ci		goto free;
6962306a36Sopenharmony_ci	}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	for (offs = MAP_OFFS_IN_BLK;
7262306a36Sopenharmony_ci	     offs < master->erasesize - sizeof(*tmpdesc);
7362306a36Sopenharmony_ci	     offs += sizeof(*tmpdesc)) {
7462306a36Sopenharmony_ci		tmpdesc = (struct sc_part_desc *)&buf[offs];
7562306a36Sopenharmony_ci		if (!scpart_desc_is_valid(tmpdesc))
7662306a36Sopenharmony_ci			break;
7762306a36Sopenharmony_ci		cnt++;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (cnt > 0) {
8162306a36Sopenharmony_ci		int bytes = cnt * sizeof(*pdesc);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
8462306a36Sopenharmony_ci		if (!pdesc) {
8562306a36Sopenharmony_ci			res = -ENOMEM;
8662306a36Sopenharmony_ci			goto free;
8762306a36Sopenharmony_ci		}
8862306a36Sopenharmony_ci		memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		*ppdesc = pdesc;
9162306a36Sopenharmony_ci		res = cnt;
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cifree:
9562306a36Sopenharmony_ci	kfree(buf);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ciout:
9862306a36Sopenharmony_ci	return res;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic int scpart_find_partmap(struct mtd_info *master,
10262306a36Sopenharmony_ci			       struct sc_part_desc **ppdesc)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	int magic_found = 0;
10562306a36Sopenharmony_ci	int res = 0;
10662306a36Sopenharmony_ci	int res2;
10762306a36Sopenharmony_ci	loff_t offs = 0;
10862306a36Sopenharmony_ci	size_t retlen;
10962306a36Sopenharmony_ci	uint8_t rdbuf[PART_MAGIC_LEN];
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	while ((magic_found < MAP_MIRROR_NUM) &&
11262306a36Sopenharmony_ci			(offs < master->size) &&
11362306a36Sopenharmony_ci			 !mtd_block_isbad(master, offs)) {
11462306a36Sopenharmony_ci		res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
11562306a36Sopenharmony_ci		if (res2 || retlen != PART_MAGIC_LEN) {
11662306a36Sopenharmony_ci			res = -EIO;
11762306a36Sopenharmony_ci			goto out;
11862306a36Sopenharmony_ci		}
11962306a36Sopenharmony_ci		if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
12062306a36Sopenharmony_ci			pr_debug("Signature found at 0x%llx\n", offs);
12162306a36Sopenharmony_ci			magic_found++;
12262306a36Sopenharmony_ci			res = scpart_scan_partmap(master, offs, ppdesc);
12362306a36Sopenharmony_ci			if (res > 0)
12462306a36Sopenharmony_ci				goto out;
12562306a36Sopenharmony_ci		}
12662306a36Sopenharmony_ci		offs += master->erasesize;
12762306a36Sopenharmony_ci	}
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ciout:
13062306a36Sopenharmony_ci	if (res > 0)
13162306a36Sopenharmony_ci		pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
13262306a36Sopenharmony_ci	else
13362306a36Sopenharmony_ci		pr_info("No valid 'SC PART MAP' was found\n");
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return res;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic int scpart_parse(struct mtd_info *master,
13962306a36Sopenharmony_ci			const struct mtd_partition **pparts,
14062306a36Sopenharmony_ci			struct mtd_part_parser_data *data)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	const char *partname;
14362306a36Sopenharmony_ci	int n;
14462306a36Sopenharmony_ci	int nr_scparts;
14562306a36Sopenharmony_ci	int nr_parts = 0;
14662306a36Sopenharmony_ci	int res = 0;
14762306a36Sopenharmony_ci	struct sc_part_desc *scpart_map = NULL;
14862306a36Sopenharmony_ci	struct mtd_partition *parts = NULL;
14962306a36Sopenharmony_ci	struct device_node *mtd_node;
15062306a36Sopenharmony_ci	struct device_node *ofpart_node;
15162306a36Sopenharmony_ci	struct device_node *pp;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	mtd_node = mtd_get_of_node(master);
15462306a36Sopenharmony_ci	if (!mtd_node) {
15562306a36Sopenharmony_ci		res = -ENOENT;
15662306a36Sopenharmony_ci		goto out;
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	ofpart_node = of_get_child_by_name(mtd_node, "partitions");
16062306a36Sopenharmony_ci	if (!ofpart_node) {
16162306a36Sopenharmony_ci		pr_info("%s: 'partitions' subnode not found on %pOF.\n",
16262306a36Sopenharmony_ci				master->name, mtd_node);
16362306a36Sopenharmony_ci		res = -ENOENT;
16462306a36Sopenharmony_ci		goto out;
16562306a36Sopenharmony_ci	}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	nr_scparts = scpart_find_partmap(master, &scpart_map);
16862306a36Sopenharmony_ci	if (nr_scparts <= 0) {
16962306a36Sopenharmony_ci		pr_info("No any partitions was found in 'SC PART MAP'.\n");
17062306a36Sopenharmony_ci		res = -ENOENT;
17162306a36Sopenharmony_ci		goto free;
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
17562306a36Sopenharmony_ci		GFP_KERNEL);
17662306a36Sopenharmony_ci	if (!parts) {
17762306a36Sopenharmony_ci		res = -ENOMEM;
17862306a36Sopenharmony_ci		goto free;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	for_each_child_of_node(ofpart_node, pp) {
18262306a36Sopenharmony_ci		u32 scpart_id;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
18562306a36Sopenharmony_ci			continue;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		for (n = 0 ; n < nr_scparts ; n++)
18862306a36Sopenharmony_ci			if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
18962306a36Sopenharmony_ci					(scpart_id == scpart_map[n].part_id))
19062306a36Sopenharmony_ci				break;
19162306a36Sopenharmony_ci		if (n >= nr_scparts)
19262306a36Sopenharmony_ci			/* not match */
19362306a36Sopenharmony_ci			continue;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci		/* add the partition found in OF into MTD partition array */
19662306a36Sopenharmony_ci		parts[nr_parts].offset = scpart_map[n].part_offs;
19762306a36Sopenharmony_ci		parts[nr_parts].size = scpart_map[n].part_bytes;
19862306a36Sopenharmony_ci		parts[nr_parts].of_node = pp;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		if (!of_property_read_string(pp, "label", &partname))
20162306a36Sopenharmony_ci			parts[nr_parts].name = partname;
20262306a36Sopenharmony_ci		if (of_property_read_bool(pp, "read-only"))
20362306a36Sopenharmony_ci			parts[nr_parts].mask_flags |= MTD_WRITEABLE;
20462306a36Sopenharmony_ci		if (of_property_read_bool(pp, "lock"))
20562306a36Sopenharmony_ci			parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		/* mark as 'done' */
20862306a36Sopenharmony_ci		scpart_map[n].part_id = ID_ALREADY_FOUND;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		nr_parts++;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (nr_parts > 0) {
21462306a36Sopenharmony_ci		*pparts = parts;
21562306a36Sopenharmony_ci		res = nr_parts;
21662306a36Sopenharmony_ci	} else
21762306a36Sopenharmony_ci		pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	of_node_put(pp);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cifree:
22262306a36Sopenharmony_ci	of_node_put(ofpart_node);
22362306a36Sopenharmony_ci	kfree(scpart_map);
22462306a36Sopenharmony_ci	if (res <= 0)
22562306a36Sopenharmony_ci		kfree(parts);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ciout:
22862306a36Sopenharmony_ci	return res;
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic const struct of_device_id scpart_parser_of_match_table[] = {
23262306a36Sopenharmony_ci	{ .compatible = "sercomm,sc-partitions" },
23362306a36Sopenharmony_ci	{},
23462306a36Sopenharmony_ci};
23562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic struct mtd_part_parser scpart_parser = {
23862306a36Sopenharmony_ci	.parse_fn = scpart_parse,
23962306a36Sopenharmony_ci	.name = "scpart",
24062306a36Sopenharmony_ci	.of_match_table = scpart_parser_of_match_table,
24162306a36Sopenharmony_ci};
24262306a36Sopenharmony_cimodule_mtd_part_parser(scpart_parser);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci/* mtd parsers will request the module by parser name */
24562306a36Sopenharmony_ciMODULE_ALIAS("scpart");
24662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
24762306a36Sopenharmony_ciMODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
24862306a36Sopenharmony_ciMODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
24962306a36Sopenharmony_ciMODULE_DESCRIPTION("Sercomm partition parser");
250