162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Qualcomm SMEM NAND flash partition parser
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2020, Linaro Ltd.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/ctype.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1162306a36Sopenharmony_ci#include <linux/mtd/partitions.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <linux/soc/qcom/smem.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define SMEM_AARM_PARTITION_TABLE	9
1662306a36Sopenharmony_ci#define SMEM_APPS			0
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define SMEM_FLASH_PART_MAGIC1		0x55ee73aa
1962306a36Sopenharmony_ci#define SMEM_FLASH_PART_MAGIC2		0xe35ebddb
2062306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_V3		3
2162306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_V4		4
2262306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_MAX_PARTS_V3	16
2362306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_MAX_PARTS_V4	48
2462306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_HDR_LEN	(4 * sizeof(u32))
2562306a36Sopenharmony_ci#define SMEM_FLASH_PTABLE_NAME_SIZE	16
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci/**
2862306a36Sopenharmony_ci * struct smem_flash_pentry - SMEM Flash partition entry
2962306a36Sopenharmony_ci * @name: Name of the partition
3062306a36Sopenharmony_ci * @offset: Offset in blocks
3162306a36Sopenharmony_ci * @length: Length of the partition in blocks
3262306a36Sopenharmony_ci * @attr: Flags for this partition
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_cistruct smem_flash_pentry {
3562306a36Sopenharmony_ci	char name[SMEM_FLASH_PTABLE_NAME_SIZE];
3662306a36Sopenharmony_ci	__le32 offset;
3762306a36Sopenharmony_ci	__le32 length;
3862306a36Sopenharmony_ci	u8 attr;
3962306a36Sopenharmony_ci} __packed __aligned(4);
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci/**
4262306a36Sopenharmony_ci * struct smem_flash_ptable - SMEM Flash partition table
4362306a36Sopenharmony_ci * @magic1: Partition table Magic 1
4462306a36Sopenharmony_ci * @magic2: Partition table Magic 2
4562306a36Sopenharmony_ci * @version: Partition table version
4662306a36Sopenharmony_ci * @numparts: Number of partitions in this ptable
4762306a36Sopenharmony_ci * @pentry: Flash partition entries belonging to this ptable
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_cistruct smem_flash_ptable {
5062306a36Sopenharmony_ci	__le32 magic1;
5162306a36Sopenharmony_ci	__le32 magic2;
5262306a36Sopenharmony_ci	__le32 version;
5362306a36Sopenharmony_ci	__le32 numparts;
5462306a36Sopenharmony_ci	struct smem_flash_pentry pentry[SMEM_FLASH_PTABLE_MAX_PARTS_V4];
5562306a36Sopenharmony_ci} __packed __aligned(4);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int parse_qcomsmem_part(struct mtd_info *mtd,
5862306a36Sopenharmony_ci			       const struct mtd_partition **pparts,
5962306a36Sopenharmony_ci			       struct mtd_part_parser_data *data)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	size_t len = SMEM_FLASH_PTABLE_HDR_LEN;
6262306a36Sopenharmony_ci	int ret, i, j, tmpparts, numparts = 0;
6362306a36Sopenharmony_ci	struct smem_flash_pentry *pentry;
6462306a36Sopenharmony_ci	struct smem_flash_ptable *ptable;
6562306a36Sopenharmony_ci	struct mtd_partition *parts;
6662306a36Sopenharmony_ci	char *name, *c;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS)
6962306a36Sopenharmony_ci			&& mtd->type == MTD_NORFLASH) {
7062306a36Sopenharmony_ci		pr_err("%s: SMEM partition parser is incompatible with 4K sectors\n",
7162306a36Sopenharmony_ci				mtd->name);
7262306a36Sopenharmony_ci		return -EINVAL;
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	pr_debug("Parsing partition table info from SMEM\n");
7662306a36Sopenharmony_ci	ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len);
7762306a36Sopenharmony_ci	if (IS_ERR(ptable)) {
7862306a36Sopenharmony_ci		if (PTR_ERR(ptable) != -EPROBE_DEFER)
7962306a36Sopenharmony_ci			pr_err("Error reading partition table header\n");
8062306a36Sopenharmony_ci		return PTR_ERR(ptable);
8162306a36Sopenharmony_ci	}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	/* Verify ptable magic */
8462306a36Sopenharmony_ci	if (le32_to_cpu(ptable->magic1) != SMEM_FLASH_PART_MAGIC1 ||
8562306a36Sopenharmony_ci	    le32_to_cpu(ptable->magic2) != SMEM_FLASH_PART_MAGIC2) {
8662306a36Sopenharmony_ci		pr_err("Partition table magic verification failed\n");
8762306a36Sopenharmony_ci		return -EINVAL;
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	/* Ensure that # of partitions is less than the max we have allocated */
9162306a36Sopenharmony_ci	tmpparts = le32_to_cpu(ptable->numparts);
9262306a36Sopenharmony_ci	if (tmpparts > SMEM_FLASH_PTABLE_MAX_PARTS_V4) {
9362306a36Sopenharmony_ci		pr_err("Partition numbers exceed the max limit\n");
9462306a36Sopenharmony_ci		return -EINVAL;
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	/* Find out length of partition data based on table version */
9862306a36Sopenharmony_ci	if (le32_to_cpu(ptable->version) <= SMEM_FLASH_PTABLE_V3) {
9962306a36Sopenharmony_ci		len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V3 *
10062306a36Sopenharmony_ci			sizeof(struct smem_flash_pentry);
10162306a36Sopenharmony_ci	} else if (le32_to_cpu(ptable->version) == SMEM_FLASH_PTABLE_V4) {
10262306a36Sopenharmony_ci		len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V4 *
10362306a36Sopenharmony_ci			sizeof(struct smem_flash_pentry);
10462306a36Sopenharmony_ci	} else {
10562306a36Sopenharmony_ci		pr_err("Unknown ptable version (%d)", le32_to_cpu(ptable->version));
10662306a36Sopenharmony_ci		return -EINVAL;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	/*
11062306a36Sopenharmony_ci	 * Now that the partition table header has been parsed, verified
11162306a36Sopenharmony_ci	 * and the length of the partition table calculated, read the
11262306a36Sopenharmony_ci	 * complete partition table
11362306a36Sopenharmony_ci	 */
11462306a36Sopenharmony_ci	ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len);
11562306a36Sopenharmony_ci	if (IS_ERR(ptable)) {
11662306a36Sopenharmony_ci		pr_err("Error reading partition table\n");
11762306a36Sopenharmony_ci		return PTR_ERR(ptable);
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for (i = 0; i < tmpparts; i++) {
12162306a36Sopenharmony_ci		pentry = &ptable->pentry[i];
12262306a36Sopenharmony_ci		if (pentry->name[0] != '\0')
12362306a36Sopenharmony_ci			numparts++;
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	parts = kcalloc(numparts, sizeof(*parts), GFP_KERNEL);
12762306a36Sopenharmony_ci	if (!parts)
12862306a36Sopenharmony_ci		return -ENOMEM;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	for (i = 0, j = 0; i < tmpparts; i++) {
13162306a36Sopenharmony_ci		pentry = &ptable->pentry[i];
13262306a36Sopenharmony_ci		if (pentry->name[0] == '\0')
13362306a36Sopenharmony_ci			continue;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci		name = kstrdup(pentry->name, GFP_KERNEL);
13662306a36Sopenharmony_ci		if (!name) {
13762306a36Sopenharmony_ci			ret = -ENOMEM;
13862306a36Sopenharmony_ci			goto out_free_parts;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		/* Convert name to lower case */
14262306a36Sopenharmony_ci		for (c = name; *c != '\0'; c++)
14362306a36Sopenharmony_ci			*c = tolower(*c);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci		parts[j].name = name;
14662306a36Sopenharmony_ci		parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize;
14762306a36Sopenharmony_ci		parts[j].mask_flags = pentry->attr;
14862306a36Sopenharmony_ci		parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize;
14962306a36Sopenharmony_ci		pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n",
15062306a36Sopenharmony_ci			 i, pentry->name, le32_to_cpu(pentry->offset),
15162306a36Sopenharmony_ci			 le32_to_cpu(pentry->length), pentry->attr);
15262306a36Sopenharmony_ci		j++;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	pr_debug("SMEM partition table found: ver: %d len: %d\n",
15662306a36Sopenharmony_ci		 le32_to_cpu(ptable->version), tmpparts);
15762306a36Sopenharmony_ci	*pparts = parts;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	return numparts;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ciout_free_parts:
16262306a36Sopenharmony_ci	while (--j >= 0)
16362306a36Sopenharmony_ci		kfree(parts[j].name);
16462306a36Sopenharmony_ci	kfree(parts);
16562306a36Sopenharmony_ci	*pparts = NULL;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return ret;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic void parse_qcomsmem_cleanup(const struct mtd_partition *pparts,
17162306a36Sopenharmony_ci				   int nr_parts)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	int i;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	for (i = 0; i < nr_parts; i++)
17662306a36Sopenharmony_ci		kfree(pparts[i].name);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	kfree(pparts);
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic const struct of_device_id qcomsmem_of_match_table[] = {
18262306a36Sopenharmony_ci	{ .compatible = "qcom,smem-part" },
18362306a36Sopenharmony_ci	{},
18462306a36Sopenharmony_ci};
18562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcomsmem_of_match_table);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic struct mtd_part_parser mtd_parser_qcomsmem = {
18862306a36Sopenharmony_ci	.parse_fn = parse_qcomsmem_part,
18962306a36Sopenharmony_ci	.cleanup = parse_qcomsmem_cleanup,
19062306a36Sopenharmony_ci	.name = "qcomsmem",
19162306a36Sopenharmony_ci	.of_match_table = qcomsmem_of_match_table,
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_cimodule_mtd_part_parser(mtd_parser_qcomsmem);
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
19662306a36Sopenharmony_ciMODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
19762306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm SMEM NAND flash partition parser");
198