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