162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Parser for TRX format partitions
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012 - 2017 Rafał Miłecki <rafal@milecki.pl>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1162306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define TRX_PARSER_MAX_PARTS		4
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/* Magics */
1662306a36Sopenharmony_ci#define TRX_MAGIC			0x30524448
1762306a36Sopenharmony_ci#define UBI_EC_MAGIC			0x23494255	/* UBI# */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistruct trx_header {
2062306a36Sopenharmony_ci	uint32_t magic;
2162306a36Sopenharmony_ci	uint32_t length;
2262306a36Sopenharmony_ci	uint32_t crc32;
2362306a36Sopenharmony_ci	uint16_t flags;
2462306a36Sopenharmony_ci	uint16_t version;
2562306a36Sopenharmony_ci	uint32_t offset[3];
2662306a36Sopenharmony_ci} __packed;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic const char *parser_trx_data_part_name(struct mtd_info *master,
2962306a36Sopenharmony_ci					     size_t offset)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	uint32_t buf;
3262306a36Sopenharmony_ci	size_t bytes_read;
3362306a36Sopenharmony_ci	int err;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	err  = mtd_read(master, offset, sizeof(buf), &bytes_read,
3662306a36Sopenharmony_ci			(uint8_t *)&buf);
3762306a36Sopenharmony_ci	if (err && !mtd_is_bitflip(err)) {
3862306a36Sopenharmony_ci		pr_err("mtd_read error while parsing (offset: 0x%zX): %d\n",
3962306a36Sopenharmony_ci			offset, err);
4062306a36Sopenharmony_ci		goto out_default;
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (buf == UBI_EC_MAGIC)
4462306a36Sopenharmony_ci		return "ubi";
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ciout_default:
4762306a36Sopenharmony_ci	return "rootfs";
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic int parser_trx_parse(struct mtd_info *mtd,
5162306a36Sopenharmony_ci			    const struct mtd_partition **pparts,
5262306a36Sopenharmony_ci			    struct mtd_part_parser_data *data)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct device_node *np = mtd_get_of_node(mtd);
5562306a36Sopenharmony_ci	struct mtd_partition *parts;
5662306a36Sopenharmony_ci	struct mtd_partition *part;
5762306a36Sopenharmony_ci	struct trx_header trx;
5862306a36Sopenharmony_ci	size_t bytes_read;
5962306a36Sopenharmony_ci	uint8_t curr_part = 0, i = 0;
6062306a36Sopenharmony_ci	uint32_t trx_magic = TRX_MAGIC;
6162306a36Sopenharmony_ci	int err;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	/* Get different magic from device tree if specified */
6462306a36Sopenharmony_ci	err = of_property_read_u32(np, "brcm,trx-magic", &trx_magic);
6562306a36Sopenharmony_ci	if (err != 0 && err != -EINVAL)
6662306a36Sopenharmony_ci		pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", err);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	parts = kcalloc(TRX_PARSER_MAX_PARTS, sizeof(struct mtd_partition),
6962306a36Sopenharmony_ci			GFP_KERNEL);
7062306a36Sopenharmony_ci	if (!parts)
7162306a36Sopenharmony_ci		return -ENOMEM;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	err = mtd_read(mtd, 0, sizeof(trx), &bytes_read, (uint8_t *)&trx);
7462306a36Sopenharmony_ci	if (err) {
7562306a36Sopenharmony_ci		pr_err("MTD reading error: %d\n", err);
7662306a36Sopenharmony_ci		kfree(parts);
7762306a36Sopenharmony_ci		return err;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (trx.magic != trx_magic) {
8162306a36Sopenharmony_ci		kfree(parts);
8262306a36Sopenharmony_ci		return -ENOENT;
8362306a36Sopenharmony_ci	}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	/* We have LZMA loader if there is address in offset[2] */
8662306a36Sopenharmony_ci	if (trx.offset[2]) {
8762306a36Sopenharmony_ci		part = &parts[curr_part++];
8862306a36Sopenharmony_ci		part->name = "loader";
8962306a36Sopenharmony_ci		part->offset = trx.offset[i];
9062306a36Sopenharmony_ci		i++;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	if (trx.offset[i]) {
9462306a36Sopenharmony_ci		part = &parts[curr_part++];
9562306a36Sopenharmony_ci		part->name = "linux";
9662306a36Sopenharmony_ci		part->offset = trx.offset[i];
9762306a36Sopenharmony_ci		i++;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (trx.offset[i]) {
10162306a36Sopenharmony_ci		part = &parts[curr_part++];
10262306a36Sopenharmony_ci		part->name = parser_trx_data_part_name(mtd, trx.offset[i]);
10362306a36Sopenharmony_ci		part->offset = trx.offset[i];
10462306a36Sopenharmony_ci		i++;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/*
10862306a36Sopenharmony_ci	 * Assume that every partition ends at the beginning of the one it is
10962306a36Sopenharmony_ci	 * followed by.
11062306a36Sopenharmony_ci	 */
11162306a36Sopenharmony_ci	for (i = 0; i < curr_part; i++) {
11262306a36Sopenharmony_ci		u64 next_part_offset = (i < curr_part - 1) ?
11362306a36Sopenharmony_ci				       parts[i + 1].offset : mtd->size;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		parts[i].size = next_part_offset - parts[i].offset;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	*pparts = parts;
11962306a36Sopenharmony_ci	return i;
12062306a36Sopenharmony_ci};
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic const struct of_device_id mtd_parser_trx_of_match_table[] = {
12362306a36Sopenharmony_ci	{ .compatible = "brcm,trx" },
12462306a36Sopenharmony_ci	{},
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtd_parser_trx_of_match_table);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic struct mtd_part_parser mtd_parser_trx = {
12962306a36Sopenharmony_ci	.parse_fn = parser_trx_parse,
13062306a36Sopenharmony_ci	.name = "trx",
13162306a36Sopenharmony_ci	.of_match_table = mtd_parser_trx_of_match_table,
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_cimodule_mtd_part_parser(mtd_parser_trx);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
13662306a36Sopenharmony_ciMODULE_DESCRIPTION("Parser for TRX format partitions");
137