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