100a35880SRafał Miłecki // SPDX-License-Identifier: GPL-2.0-only
200a35880SRafał Miłecki /*
300a35880SRafał Miłecki * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
400a35880SRafał Miłecki */
500a35880SRafał Miłecki
600a35880SRafał Miłecki #include <linux/kernel.h>
700a35880SRafał Miłecki #include <linux/module.h>
800a35880SRafał Miłecki #include <linux/mtd/mtd.h>
900a35880SRafał Miłecki #include <linux/mtd/partitions.h>
1000a35880SRafał Miłecki #include <linux/of.h>
1100a35880SRafał Miłecki #include <linux/slab.h>
1200a35880SRafał Miłecki
1300a35880SRafał Miłecki #define TPLINK_SAFELOADER_DATA_OFFSET 4
1400a35880SRafał Miłecki #define TPLINK_SAFELOADER_MAX_PARTS 32
1500a35880SRafał Miłecki
1600a35880SRafał Miłecki struct safeloader_cmn_header {
1700a35880SRafał Miłecki __be32 size;
1800a35880SRafał Miłecki uint32_t unused;
1900a35880SRafał Miłecki } __packed;
2000a35880SRafał Miłecki
mtd_parser_tplink_safeloader_read_table(struct mtd_info * mtd)2100a35880SRafał Miłecki static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
2200a35880SRafał Miłecki {
2300a35880SRafał Miłecki struct safeloader_cmn_header hdr;
2400a35880SRafał Miłecki struct device_node *np;
2500a35880SRafał Miłecki size_t bytes_read;
2600a35880SRafał Miłecki size_t size;
276c0a15a3SDan Carpenter u32 offset;
2800a35880SRafał Miłecki char *buf;
2900a35880SRafał Miłecki int err;
3000a35880SRafał Miłecki
3100a35880SRafał Miłecki np = mtd_get_of_node(mtd);
3200a35880SRafał Miłecki if (mtd_is_partition(mtd))
3300a35880SRafał Miłecki of_node_get(np);
3400a35880SRafał Miłecki else
3500a35880SRafał Miłecki np = of_get_child_by_name(np, "partitions");
3600a35880SRafał Miłecki
376c0a15a3SDan Carpenter if (of_property_read_u32(np, "partitions-table-offset", &offset)) {
3800a35880SRafał Miłecki pr_err("Failed to get partitions table offset\n");
3900a35880SRafał Miłecki goto err_put;
4000a35880SRafał Miłecki }
4100a35880SRafał Miłecki
4200a35880SRafał Miłecki err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
4300a35880SRafał Miłecki if (err && !mtd_is_bitflip(err)) {
446c0a15a3SDan Carpenter pr_err("Failed to read from %s at 0x%x\n", mtd->name, offset);
4500a35880SRafał Miłecki goto err_put;
4600a35880SRafał Miłecki }
4700a35880SRafał Miłecki
4800a35880SRafał Miłecki size = be32_to_cpu(hdr.size);
4900a35880SRafał Miłecki
5000a35880SRafał Miłecki buf = kmalloc(size + 1, GFP_KERNEL);
5100a35880SRafał Miłecki if (!buf)
5200a35880SRafał Miłecki goto err_put;
5300a35880SRafał Miłecki
5400a35880SRafał Miłecki err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
5500a35880SRafał Miłecki if (err && !mtd_is_bitflip(err)) {
5600a35880SRafał Miłecki pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
5700a35880SRafał Miłecki goto err_kfree;
5800a35880SRafał Miłecki }
5900a35880SRafał Miłecki
6000a35880SRafał Miłecki buf[size] = '\0';
6100a35880SRafał Miłecki
6200a35880SRafał Miłecki of_node_put(np);
6300a35880SRafał Miłecki
6400a35880SRafał Miłecki return buf;
6500a35880SRafał Miłecki
6600a35880SRafał Miłecki err_kfree:
6700a35880SRafał Miłecki kfree(buf);
6800a35880SRafał Miłecki err_put:
6900a35880SRafał Miłecki of_node_put(np);
7000a35880SRafał Miłecki return NULL;
7100a35880SRafał Miłecki }
7200a35880SRafał Miłecki
mtd_parser_tplink_safeloader_parse(struct mtd_info * mtd,const struct mtd_partition ** pparts,struct mtd_part_parser_data * data)7300a35880SRafał Miłecki static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
7400a35880SRafał Miłecki const struct mtd_partition **pparts,
7500a35880SRafał Miłecki struct mtd_part_parser_data *data)
7600a35880SRafał Miłecki {
7700a35880SRafał Miłecki struct mtd_partition *parts;
7800a35880SRafał Miłecki char name[65];
7900a35880SRafał Miłecki size_t offset;
8000a35880SRafał Miłecki size_t bytes;
8100a35880SRafał Miłecki char *buf;
8200a35880SRafał Miłecki int idx;
8300a35880SRafał Miłecki int err;
8400a35880SRafał Miłecki
8500a35880SRafał Miłecki parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
8600a35880SRafał Miłecki if (!parts) {
8700a35880SRafał Miłecki err = -ENOMEM;
8800a35880SRafał Miłecki goto err_out;
8900a35880SRafał Miłecki }
9000a35880SRafał Miłecki
9100a35880SRafał Miłecki buf = mtd_parser_tplink_safeloader_read_table(mtd);
9200a35880SRafał Miłecki if (!buf) {
9300a35880SRafał Miłecki err = -ENOENT;
94*7adde5acSYuan Can goto err_free_parts;
9500a35880SRafał Miłecki }
9600a35880SRafał Miłecki
9700a35880SRafał Miłecki for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
9800a35880SRafał Miłecki idx < TPLINK_SAFELOADER_MAX_PARTS &&
9900a35880SRafał Miłecki sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
10000a35880SRafał Miłecki name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
10100a35880SRafał Miłecki idx++, offset += bytes + 1) {
10200a35880SRafał Miłecki parts[idx].name = kstrdup(name, GFP_KERNEL);
10300a35880SRafał Miłecki if (!parts[idx].name) {
10400a35880SRafał Miłecki err = -ENOMEM;
10500a35880SRafał Miłecki goto err_free;
10600a35880SRafał Miłecki }
10700a35880SRafał Miłecki }
10800a35880SRafał Miłecki
10900a35880SRafał Miłecki if (idx == TPLINK_SAFELOADER_MAX_PARTS)
11000a35880SRafał Miłecki pr_warn("Reached maximum number of partitions!\n");
11100a35880SRafał Miłecki
11200a35880SRafał Miłecki kfree(buf);
11300a35880SRafał Miłecki
11400a35880SRafał Miłecki *pparts = parts;
11500a35880SRafał Miłecki
11600a35880SRafał Miłecki return idx;
11700a35880SRafał Miłecki
11800a35880SRafał Miłecki err_free:
11900a35880SRafał Miłecki for (idx -= 1; idx >= 0; idx--)
12000a35880SRafał Miłecki kfree(parts[idx].name);
121*7adde5acSYuan Can err_free_parts:
122*7adde5acSYuan Can kfree(parts);
12300a35880SRafał Miłecki err_out:
12400a35880SRafał Miłecki return err;
12500a35880SRafał Miłecki };
12600a35880SRafał Miłecki
mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition * pparts,int nr_parts)12700a35880SRafał Miłecki static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
12800a35880SRafał Miłecki int nr_parts)
12900a35880SRafał Miłecki {
13000a35880SRafał Miłecki int i;
13100a35880SRafał Miłecki
13200a35880SRafał Miłecki for (i = 0; i < nr_parts; i++)
13300a35880SRafał Miłecki kfree(pparts[i].name);
13400a35880SRafał Miłecki
13500a35880SRafał Miłecki kfree(pparts);
13600a35880SRafał Miłecki }
13700a35880SRafał Miłecki
13800a35880SRafał Miłecki static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
13900a35880SRafał Miłecki { .compatible = "tplink,safeloader-partitions" },
14000a35880SRafał Miłecki {},
14100a35880SRafał Miłecki };
14200a35880SRafał Miłecki MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);
14300a35880SRafał Miłecki
14400a35880SRafał Miłecki static struct mtd_part_parser mtd_parser_tplink_safeloader = {
14500a35880SRafał Miłecki .parse_fn = mtd_parser_tplink_safeloader_parse,
14600a35880SRafał Miłecki .cleanup = mtd_parser_tplink_safeloader_cleanup,
14700a35880SRafał Miłecki .name = "tplink-safeloader",
14800a35880SRafał Miłecki .of_match_table = mtd_parser_tplink_safeloader_of_match_table,
14900a35880SRafał Miłecki };
15000a35880SRafał Miłecki module_mtd_part_parser(mtd_parser_tplink_safeloader);
15100a35880SRafał Miłecki
15200a35880SRafał Miłecki MODULE_LICENSE("GPL");
153