162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
962306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/slab.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define TPLINK_SAFELOADER_DATA_OFFSET		4
1462306a36Sopenharmony_ci#define TPLINK_SAFELOADER_MAX_PARTS		32
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistruct safeloader_cmn_header {
1762306a36Sopenharmony_ci	__be32 size;
1862306a36Sopenharmony_ci	uint32_t unused;
1962306a36Sopenharmony_ci} __packed;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	struct safeloader_cmn_header hdr;
2462306a36Sopenharmony_ci	struct device_node *np;
2562306a36Sopenharmony_ci	size_t bytes_read;
2662306a36Sopenharmony_ci	size_t size;
2762306a36Sopenharmony_ci	u32 offset;
2862306a36Sopenharmony_ci	char *buf;
2962306a36Sopenharmony_ci	int err;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	np = mtd_get_of_node(mtd);
3262306a36Sopenharmony_ci	if (mtd_is_partition(mtd))
3362306a36Sopenharmony_ci		of_node_get(np);
3462306a36Sopenharmony_ci	else
3562306a36Sopenharmony_ci		np = of_get_child_by_name(np, "partitions");
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (of_property_read_u32(np, "partitions-table-offset", &offset)) {
3862306a36Sopenharmony_ci		pr_err("Failed to get partitions table offset\n");
3962306a36Sopenharmony_ci		goto err_put;
4062306a36Sopenharmony_ci	}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
4362306a36Sopenharmony_ci	if (err && !mtd_is_bitflip(err)) {
4462306a36Sopenharmony_ci		pr_err("Failed to read from %s at 0x%x\n", mtd->name, offset);
4562306a36Sopenharmony_ci		goto err_put;
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	size = be32_to_cpu(hdr.size);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	buf = kmalloc(size + 1, GFP_KERNEL);
5162306a36Sopenharmony_ci	if (!buf)
5262306a36Sopenharmony_ci		goto err_put;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
5562306a36Sopenharmony_ci	if (err && !mtd_is_bitflip(err)) {
5662306a36Sopenharmony_ci		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
5762306a36Sopenharmony_ci		goto err_kfree;
5862306a36Sopenharmony_ci	}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	buf[size] = '\0';
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	of_node_put(np);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return buf;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cierr_kfree:
6762306a36Sopenharmony_ci	kfree(buf);
6862306a36Sopenharmony_cierr_put:
6962306a36Sopenharmony_ci	of_node_put(np);
7062306a36Sopenharmony_ci	return NULL;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
7462306a36Sopenharmony_ci					      const struct mtd_partition **pparts,
7562306a36Sopenharmony_ci					      struct mtd_part_parser_data *data)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	struct mtd_partition *parts;
7862306a36Sopenharmony_ci	char name[65];
7962306a36Sopenharmony_ci	size_t offset;
8062306a36Sopenharmony_ci	size_t bytes;
8162306a36Sopenharmony_ci	char *buf;
8262306a36Sopenharmony_ci	int idx;
8362306a36Sopenharmony_ci	int err;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
8662306a36Sopenharmony_ci	if (!parts) {
8762306a36Sopenharmony_ci		err = -ENOMEM;
8862306a36Sopenharmony_ci		goto err_out;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	buf = mtd_parser_tplink_safeloader_read_table(mtd);
9262306a36Sopenharmony_ci	if (!buf) {
9362306a36Sopenharmony_ci		err = -ENOENT;
9462306a36Sopenharmony_ci		goto err_free_parts;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
9862306a36Sopenharmony_ci	     idx < TPLINK_SAFELOADER_MAX_PARTS &&
9962306a36Sopenharmony_ci	     sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
10062306a36Sopenharmony_ci		    name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
10162306a36Sopenharmony_ci	     idx++, offset += bytes + 1) {
10262306a36Sopenharmony_ci		parts[idx].name = kstrdup(name, GFP_KERNEL);
10362306a36Sopenharmony_ci		if (!parts[idx].name) {
10462306a36Sopenharmony_ci			err = -ENOMEM;
10562306a36Sopenharmony_ci			goto err_free;
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (idx == TPLINK_SAFELOADER_MAX_PARTS)
11062306a36Sopenharmony_ci		pr_warn("Reached maximum number of partitions!\n");
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	kfree(buf);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	*pparts = parts;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	return idx;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cierr_free:
11962306a36Sopenharmony_ci	for (idx -= 1; idx >= 0; idx--)
12062306a36Sopenharmony_ci		kfree(parts[idx].name);
12162306a36Sopenharmony_cierr_free_parts:
12262306a36Sopenharmony_ci	kfree(parts);
12362306a36Sopenharmony_cierr_out:
12462306a36Sopenharmony_ci	return err;
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
12862306a36Sopenharmony_ci						 int nr_parts)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	int i;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	for (i = 0; i < nr_parts; i++)
13362306a36Sopenharmony_ci		kfree(pparts[i].name);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	kfree(pparts);
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
13962306a36Sopenharmony_ci	{ .compatible = "tplink,safeloader-partitions" },
14062306a36Sopenharmony_ci	{},
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic struct mtd_part_parser mtd_parser_tplink_safeloader = {
14562306a36Sopenharmony_ci	.parse_fn = mtd_parser_tplink_safeloader_parse,
14662306a36Sopenharmony_ci	.cleanup = mtd_parser_tplink_safeloader_cleanup,
14762306a36Sopenharmony_ci	.name = "tplink-safeloader",
14862306a36Sopenharmony_ci	.of_match_table = mtd_parser_tplink_safeloader_of_match_table,
14962306a36Sopenharmony_ci};
15062306a36Sopenharmony_cimodule_mtd_part_parser(mtd_parser_tplink_safeloader);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
153