Commit 00a35880 authored by Rafał Miłecki's avatar Rafał Miłecki Committed by Miquel Raynal
Browse files

mtd: parsers: add TP-Link SafeLoader partitions table parser



This parser deals with most TP-Link home routers. It reads info about
partitions and registers them in the MTD subsystem.

Example from TP-Link Archer C5 V2:

spi-nor spi0.0: s25fl128s1 (16384 Kbytes)
15 tplink-safeloader partitions found on MTD device spi0.0
Creating 15 MTD partitions on "spi0.0":
0x000000000000-0x000000040000 : "fs-uboot"
0x000000040000-0x000000440000 : "os-image"
0x000000440000-0x000000e40000 : "rootfs"
0x000000e40000-0x000000e40200 : "default-mac"
0x000000e40200-0x000000e40400 : "pin"
0x000000e40400-0x000000e40600 : "product-info"
0x000000e50000-0x000000e60000 : "partition-table"
0x000000e60000-0x000000e60200 : "soft-version"
0x000000e61000-0x000000e70000 : "support-list"
0x000000e70000-0x000000e80000 : "profile"
0x000000e80000-0x000000e90000 : "default-config"
0x000000e90000-0x000000ee0000 : "user-config"
0x000000ee0000-0x000000fe0000 : "log"
0x000000fe0000-0x000000ff0000 : "radio_bk"
0x000000ff0000-0x000001000000 : "radio"

Signed-off-by: default avatarRafał Miłecki <rafal@milecki.pl>
Signed-off-by: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20221015092950.27467-2-zajec5@gmail.com
parent 132c57b4
Loading
Loading
Loading
Loading
+15 −0
Original line number Original line Diff line number Diff line
@@ -123,6 +123,21 @@ config MTD_AFS_PARTS
	  for your particular device. It won't happen automatically. The
	  for your particular device. It won't happen automatically. The
	  'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.
	  'physmap' map driver (CONFIG_MTD_PHYSMAP) does this, for example.


config MTD_PARSER_TPLINK_SAFELOADER
	tristate "TP-Link Safeloader partitions parser"
	depends on MTD && (ARCH_BCM_5301X || ATH79 || SOC_MT7620 || SOC_MT7621 || COMPILE_TEST)
	help
	  TP-Link home routers use flash partitions to store various data. Info
	  about flash space layout is stored in a partitions table using a
	  custom ASCII-based format.

	  That format was first found in devices with SafeLoader bootloader and
	  was named after it. Later it was adapted to CFE and U-Boot
	  bootloaders.

	  This driver reads partitions table, parses it and creates MTD
	  partitions.

config MTD_PARSER_TRX
config MTD_PARSER_TRX
	tristate "Parser for TRX format partitions"
	tristate "Parser for TRX format partitions"
	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
	depends on MTD && (BCM47XX || ARCH_BCM_5301X || ARCH_MEDIATEK || RALINK || COMPILE_TEST)
+1 −0
Original line number Original line Diff line number Diff line
@@ -10,6 +10,7 @@ ofpart-$(CONFIG_MTD_OF_PARTS_BCM4908) += ofpart_bcm4908.o
ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
ofpart-$(CONFIG_MTD_OF_PARTS_LINKSYS_NS)+= ofpart_linksys_ns.o
obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
obj-$(CONFIG_MTD_PARSER_IMAGETAG)	+= parser_imagetag.o
obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
obj-$(CONFIG_MTD_AFS_PARTS)		+= afs.o
obj-$(CONFIG_MTD_PARSER_TPLINK_SAFELOADER)	+= tplink_safeloader.o
obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
obj-$(CONFIG_MTD_PARSER_TRX)		+= parser_trx.o
obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
obj-$(CONFIG_MTD_SERCOMM_PARTS)		+= scpart.o
obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
obj-$(CONFIG_MTD_SHARPSL_PARTS)		+= sharpslpart.o
+150 −0
Original line number Original line Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright © 2022 Rafał Miłecki <rafal@milecki.pl>
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/slab.h>

#define TPLINK_SAFELOADER_DATA_OFFSET		4
#define TPLINK_SAFELOADER_MAX_PARTS		32

struct safeloader_cmn_header {
	__be32 size;
	uint32_t unused;
} __packed;

static void *mtd_parser_tplink_safeloader_read_table(struct mtd_info *mtd)
{
	struct safeloader_cmn_header hdr;
	struct device_node *np;
	size_t bytes_read;
	size_t offset;
	size_t size;
	char *buf;
	int err;

	np = mtd_get_of_node(mtd);
	if (mtd_is_partition(mtd))
		of_node_get(np);
	else
		np = of_get_child_by_name(np, "partitions");

	if (of_property_read_u32(np, "partitions-table-offset", (u32 *)&offset)) {
		pr_err("Failed to get partitions table offset\n");
		goto err_put;
	}

	err = mtd_read(mtd, offset, sizeof(hdr), &bytes_read, (uint8_t *)&hdr);
	if (err && !mtd_is_bitflip(err)) {
		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset);
		goto err_put;
	}

	size = be32_to_cpu(hdr.size);

	buf = kmalloc(size + 1, GFP_KERNEL);
	if (!buf)
		goto err_put;

	err = mtd_read(mtd, offset + sizeof(hdr), size, &bytes_read, buf);
	if (err && !mtd_is_bitflip(err)) {
		pr_err("Failed to read from %s at 0x%zx\n", mtd->name, offset + sizeof(hdr));
		goto err_kfree;
	}

	buf[size] = '\0';

	of_node_put(np);

	return buf;

err_kfree:
	kfree(buf);
err_put:
	of_node_put(np);
	return NULL;
}

static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
					      const struct mtd_partition **pparts,
					      struct mtd_part_parser_data *data)
{
	struct mtd_partition *parts;
	char name[65];
	size_t offset;
	size_t bytes;
	char *buf;
	int idx;
	int err;

	parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
	if (!parts) {
		err = -ENOMEM;
		goto err_out;
	}

	buf = mtd_parser_tplink_safeloader_read_table(mtd);
	if (!buf) {
		err = -ENOENT;
		goto err_out;
	}

	for (idx = 0, offset = TPLINK_SAFELOADER_DATA_OFFSET;
	     idx < TPLINK_SAFELOADER_MAX_PARTS &&
	     sscanf(buf + offset, "partition %64s base 0x%llx size 0x%llx%zn\n",
		    name, &parts[idx].offset, &parts[idx].size, &bytes) == 3;
	     idx++, offset += bytes + 1) {
		parts[idx].name = kstrdup(name, GFP_KERNEL);
		if (!parts[idx].name) {
			err = -ENOMEM;
			goto err_free;
		}
	}

	if (idx == TPLINK_SAFELOADER_MAX_PARTS)
		pr_warn("Reached maximum number of partitions!\n");

	kfree(buf);

	*pparts = parts;

	return idx;

err_free:
	for (idx -= 1; idx >= 0; idx--)
		kfree(parts[idx].name);
err_out:
	return err;
};

static void mtd_parser_tplink_safeloader_cleanup(const struct mtd_partition *pparts,
						 int nr_parts)
{
	int i;

	for (i = 0; i < nr_parts; i++)
		kfree(pparts[i].name);

	kfree(pparts);
}

static const struct of_device_id mtd_parser_tplink_safeloader_of_match_table[] = {
	{ .compatible = "tplink,safeloader-partitions" },
	{},
};
MODULE_DEVICE_TABLE(of, mtd_parser_tplink_safeloader_of_match_table);

static struct mtd_part_parser mtd_parser_tplink_safeloader = {
	.parse_fn = mtd_parser_tplink_safeloader_parse,
	.cleanup = mtd_parser_tplink_safeloader_cleanup,
	.name = "tplink-safeloader",
	.of_match_table = mtd_parser_tplink_safeloader_of_match_table,
};
module_mtd_part_parser(mtd_parser_tplink_safeloader);

MODULE_LICENSE("GPL");