|  | // 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"); |