| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * Qualcomm SMEM NAND flash partition parser | 
 |  * | 
 |  * Copyright (C) 2020, Linaro Ltd. | 
 |  */ | 
 |  | 
 | #include <linux/ctype.h> | 
 | #include <linux/module.h> | 
 | #include <linux/mtd/mtd.h> | 
 | #include <linux/mtd/partitions.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/soc/qcom/smem.h> | 
 |  | 
 | #define SMEM_AARM_PARTITION_TABLE	9 | 
 | #define SMEM_APPS			0 | 
 |  | 
 | #define SMEM_FLASH_PART_MAGIC1		0x55ee73aa | 
 | #define SMEM_FLASH_PART_MAGIC2		0xe35ebddb | 
 | #define SMEM_FLASH_PTABLE_V3		3 | 
 | #define SMEM_FLASH_PTABLE_V4		4 | 
 | #define SMEM_FLASH_PTABLE_MAX_PARTS_V3	16 | 
 | #define SMEM_FLASH_PTABLE_MAX_PARTS_V4	48 | 
 | #define SMEM_FLASH_PTABLE_HDR_LEN	(4 * sizeof(u32)) | 
 | #define SMEM_FLASH_PTABLE_NAME_SIZE	16 | 
 |  | 
 | /** | 
 |  * struct smem_flash_pentry - SMEM Flash partition entry | 
 |  * @name: Name of the partition | 
 |  * @offset: Offset in blocks | 
 |  * @length: Length of the partition in blocks | 
 |  * @attr: Flags for this partition | 
 |  */ | 
 | struct smem_flash_pentry { | 
 | 	char name[SMEM_FLASH_PTABLE_NAME_SIZE]; | 
 | 	__le32 offset; | 
 | 	__le32 length; | 
 | 	u8 attr; | 
 | } __packed __aligned(4); | 
 |  | 
 | /** | 
 |  * struct smem_flash_ptable - SMEM Flash partition table | 
 |  * @magic1: Partition table Magic 1 | 
 |  * @magic2: Partition table Magic 2 | 
 |  * @version: Partition table version | 
 |  * @numparts: Number of partitions in this ptable | 
 |  * @pentry: Flash partition entries belonging to this ptable | 
 |  */ | 
 | struct smem_flash_ptable { | 
 | 	__le32 magic1; | 
 | 	__le32 magic2; | 
 | 	__le32 version; | 
 | 	__le32 numparts; | 
 | 	struct smem_flash_pentry pentry[SMEM_FLASH_PTABLE_MAX_PARTS_V4]; | 
 | } __packed __aligned(4); | 
 |  | 
 | static int parse_qcomsmem_part(struct mtd_info *mtd, | 
 | 			       const struct mtd_partition **pparts, | 
 | 			       struct mtd_part_parser_data *data) | 
 | { | 
 | 	size_t len = SMEM_FLASH_PTABLE_HDR_LEN; | 
 | 	int ret, i, j, tmpparts, numparts = 0; | 
 | 	struct smem_flash_pentry *pentry; | 
 | 	struct smem_flash_ptable *ptable; | 
 | 	struct mtd_partition *parts; | 
 | 	char *name, *c; | 
 |  | 
 | 	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS) | 
 | 			&& mtd->type == MTD_NORFLASH) { | 
 | 		pr_err("%s: SMEM partition parser is incompatible with 4K sectors\n", | 
 | 				mtd->name); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	pr_debug("Parsing partition table info from SMEM\n"); | 
 | 	ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len); | 
 | 	if (IS_ERR(ptable)) { | 
 | 		if (PTR_ERR(ptable) != -EPROBE_DEFER) | 
 | 			pr_err("Error reading partition table header\n"); | 
 | 		return PTR_ERR(ptable); | 
 | 	} | 
 |  | 
 | 	/* Verify ptable magic */ | 
 | 	if (le32_to_cpu(ptable->magic1) != SMEM_FLASH_PART_MAGIC1 || | 
 | 	    le32_to_cpu(ptable->magic2) != SMEM_FLASH_PART_MAGIC2) { | 
 | 		pr_err("Partition table magic verification failed\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Ensure that # of partitions is less than the max we have allocated */ | 
 | 	tmpparts = le32_to_cpu(ptable->numparts); | 
 | 	if (tmpparts > SMEM_FLASH_PTABLE_MAX_PARTS_V4) { | 
 | 		pr_err("Partition numbers exceed the max limit\n"); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* Find out length of partition data based on table version */ | 
 | 	if (le32_to_cpu(ptable->version) <= SMEM_FLASH_PTABLE_V3) { | 
 | 		len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V3 * | 
 | 			sizeof(struct smem_flash_pentry); | 
 | 	} else if (le32_to_cpu(ptable->version) == SMEM_FLASH_PTABLE_V4) { | 
 | 		len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V4 * | 
 | 			sizeof(struct smem_flash_pentry); | 
 | 	} else { | 
 | 		pr_err("Unknown ptable version (%d)", le32_to_cpu(ptable->version)); | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	/* | 
 | 	 * Now that the partition table header has been parsed, verified | 
 | 	 * and the length of the partition table calculated, read the | 
 | 	 * complete partition table | 
 | 	 */ | 
 | 	ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, &len); | 
 | 	if (IS_ERR(ptable)) { | 
 | 		pr_err("Error reading partition table\n"); | 
 | 		return PTR_ERR(ptable); | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < tmpparts; i++) { | 
 | 		pentry = &ptable->pentry[i]; | 
 | 		if (pentry->name[0] != '\0') | 
 | 			numparts++; | 
 | 	} | 
 |  | 
 | 	parts = kcalloc(numparts, sizeof(*parts), GFP_KERNEL); | 
 | 	if (!parts) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	for (i = 0, j = 0; i < tmpparts; i++) { | 
 | 		pentry = &ptable->pentry[i]; | 
 | 		if (pentry->name[0] == '\0') | 
 | 			continue; | 
 |  | 
 | 		name = kstrdup(pentry->name, GFP_KERNEL); | 
 | 		if (!name) { | 
 | 			ret = -ENOMEM; | 
 | 			goto out_free_parts; | 
 | 		} | 
 |  | 
 | 		/* Convert name to lower case */ | 
 | 		for (c = name; *c != '\0'; c++) | 
 | 			*c = tolower(*c); | 
 |  | 
 | 		parts[j].name = name; | 
 | 		parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize; | 
 | 		parts[j].mask_flags = pentry->attr; | 
 | 		parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize; | 
 | 		pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n", | 
 | 			 i, pentry->name, le32_to_cpu(pentry->offset), | 
 | 			 le32_to_cpu(pentry->length), pentry->attr); | 
 | 		j++; | 
 | 	} | 
 |  | 
 | 	pr_debug("SMEM partition table found: ver: %d len: %d\n", | 
 | 		 le32_to_cpu(ptable->version), tmpparts); | 
 | 	*pparts = parts; | 
 |  | 
 | 	return numparts; | 
 |  | 
 | out_free_parts: | 
 | 	while (--j >= 0) | 
 | 		kfree(parts[j].name); | 
 | 	kfree(parts); | 
 | 	*pparts = NULL; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void parse_qcomsmem_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 qcomsmem_of_match_table[] = { | 
 | 	{ .compatible = "qcom,smem-part" }, | 
 | 	{}, | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, qcomsmem_of_match_table); | 
 |  | 
 | static struct mtd_part_parser mtd_parser_qcomsmem = { | 
 | 	.parse_fn = parse_qcomsmem_part, | 
 | 	.cleanup = parse_qcomsmem_cleanup, | 
 | 	.name = "qcomsmem", | 
 | 	.of_match_table = qcomsmem_of_match_table, | 
 | }; | 
 | module_mtd_part_parser(mtd_parser_qcomsmem); | 
 |  | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); | 
 | MODULE_DESCRIPTION("Qualcomm SMEM NAND flash partition parser"); |