1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Qualcomm SMEM NAND flash partition parser |
4 | * |
5 | * Copyright (C) 2020, Linaro Ltd. |
6 | */ |
7 | |
8 | #include <linux/ctype.h> |
9 | #include <linux/module.h> |
10 | #include <linux/mtd/mtd.h> |
11 | #include <linux/mtd/partitions.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/soc/qcom/smem.h> |
14 | |
15 | #define SMEM_AARM_PARTITION_TABLE 9 |
16 | #define SMEM_APPS 0 |
17 | |
18 | #define SMEM_FLASH_PART_MAGIC1 0x55ee73aa |
19 | #define SMEM_FLASH_PART_MAGIC2 0xe35ebddb |
20 | #define SMEM_FLASH_PTABLE_V3 3 |
21 | #define SMEM_FLASH_PTABLE_V4 4 |
22 | #define SMEM_FLASH_PTABLE_MAX_PARTS_V3 16 |
23 | #define SMEM_FLASH_PTABLE_MAX_PARTS_V4 48 |
24 | #define SMEM_FLASH_PTABLE_HDR_LEN (4 * sizeof(u32)) |
25 | #define SMEM_FLASH_PTABLE_NAME_SIZE 16 |
26 | |
27 | /** |
28 | * struct smem_flash_pentry - SMEM Flash partition entry |
29 | * @name: Name of the partition |
30 | * @offset: Offset in blocks |
31 | * @length: Length of the partition in blocks |
32 | * @attr: Flags for this partition |
33 | */ |
34 | struct smem_flash_pentry { |
35 | char name[SMEM_FLASH_PTABLE_NAME_SIZE]; |
36 | __le32 offset; |
37 | __le32 length; |
38 | u8 attr; |
39 | } __packed __aligned(4); |
40 | |
41 | /** |
42 | * struct smem_flash_ptable - SMEM Flash partition table |
43 | * @magic1: Partition table Magic 1 |
44 | * @magic2: Partition table Magic 2 |
45 | * @version: Partition table version |
46 | * @numparts: Number of partitions in this ptable |
47 | * @pentry: Flash partition entries belonging to this ptable |
48 | */ |
49 | struct smem_flash_ptable { |
50 | __le32 magic1; |
51 | __le32 magic2; |
52 | __le32 version; |
53 | __le32 numparts; |
54 | struct smem_flash_pentry pentry[SMEM_FLASH_PTABLE_MAX_PARTS_V4]; |
55 | } __packed __aligned(4); |
56 | |
57 | static int parse_qcomsmem_part(struct mtd_info *mtd, |
58 | const struct mtd_partition **pparts, |
59 | struct mtd_part_parser_data *data) |
60 | { |
61 | size_t len = SMEM_FLASH_PTABLE_HDR_LEN; |
62 | int ret, i, j, tmpparts, numparts = 0; |
63 | struct smem_flash_pentry *pentry; |
64 | struct smem_flash_ptable *ptable; |
65 | struct mtd_partition *parts; |
66 | char *name, *c; |
67 | |
68 | if (IS_ENABLED(CONFIG_MTD_SPI_NOR_USE_4K_SECTORS) |
69 | && mtd->type == MTD_NORFLASH) { |
70 | pr_err("%s: SMEM partition parser is incompatible with 4K sectors\n" , |
71 | mtd->name); |
72 | return -EINVAL; |
73 | } |
74 | |
75 | pr_debug("Parsing partition table info from SMEM\n" ); |
76 | ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, size: &len); |
77 | if (IS_ERR(ptr: ptable)) { |
78 | if (PTR_ERR(ptr: ptable) != -EPROBE_DEFER) |
79 | pr_err("Error reading partition table header\n" ); |
80 | return PTR_ERR(ptr: ptable); |
81 | } |
82 | |
83 | /* Verify ptable magic */ |
84 | if (le32_to_cpu(ptable->magic1) != SMEM_FLASH_PART_MAGIC1 || |
85 | le32_to_cpu(ptable->magic2) != SMEM_FLASH_PART_MAGIC2) { |
86 | pr_err("Partition table magic verification failed\n" ); |
87 | return -EINVAL; |
88 | } |
89 | |
90 | /* Ensure that # of partitions is less than the max we have allocated */ |
91 | tmpparts = le32_to_cpu(ptable->numparts); |
92 | if (tmpparts > SMEM_FLASH_PTABLE_MAX_PARTS_V4) { |
93 | pr_err("Partition numbers exceed the max limit\n" ); |
94 | return -EINVAL; |
95 | } |
96 | |
97 | /* Find out length of partition data based on table version */ |
98 | if (le32_to_cpu(ptable->version) <= SMEM_FLASH_PTABLE_V3) { |
99 | len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V3 * |
100 | sizeof(struct smem_flash_pentry); |
101 | } else if (le32_to_cpu(ptable->version) == SMEM_FLASH_PTABLE_V4) { |
102 | len = SMEM_FLASH_PTABLE_HDR_LEN + SMEM_FLASH_PTABLE_MAX_PARTS_V4 * |
103 | sizeof(struct smem_flash_pentry); |
104 | } else { |
105 | pr_err("Unknown ptable version (%d)" , le32_to_cpu(ptable->version)); |
106 | return -EINVAL; |
107 | } |
108 | |
109 | /* |
110 | * Now that the partition table header has been parsed, verified |
111 | * and the length of the partition table calculated, read the |
112 | * complete partition table |
113 | */ |
114 | ptable = qcom_smem_get(SMEM_APPS, SMEM_AARM_PARTITION_TABLE, size: &len); |
115 | if (IS_ERR(ptr: ptable)) { |
116 | pr_err("Error reading partition table\n" ); |
117 | return PTR_ERR(ptr: ptable); |
118 | } |
119 | |
120 | for (i = 0; i < tmpparts; i++) { |
121 | pentry = &ptable->pentry[i]; |
122 | if (pentry->name[0] != '\0') |
123 | numparts++; |
124 | } |
125 | |
126 | parts = kcalloc(n: numparts, size: sizeof(*parts), GFP_KERNEL); |
127 | if (!parts) |
128 | return -ENOMEM; |
129 | |
130 | for (i = 0, j = 0; i < tmpparts; i++) { |
131 | pentry = &ptable->pentry[i]; |
132 | if (pentry->name[0] == '\0') |
133 | continue; |
134 | |
135 | name = kstrdup(s: pentry->name, GFP_KERNEL); |
136 | if (!name) { |
137 | ret = -ENOMEM; |
138 | goto out_free_parts; |
139 | } |
140 | |
141 | /* Convert name to lower case */ |
142 | for (c = name; *c != '\0'; c++) |
143 | *c = tolower(*c); |
144 | |
145 | parts[j].name = name; |
146 | parts[j].offset = le32_to_cpu(pentry->offset) * mtd->erasesize; |
147 | parts[j].mask_flags = pentry->attr; |
148 | parts[j].size = le32_to_cpu(pentry->length) * mtd->erasesize; |
149 | pr_debug("%d: %s offs=0x%08x size=0x%08x attr:0x%08x\n" , |
150 | i, pentry->name, le32_to_cpu(pentry->offset), |
151 | le32_to_cpu(pentry->length), pentry->attr); |
152 | j++; |
153 | } |
154 | |
155 | pr_debug("SMEM partition table found: ver: %d len: %d\n" , |
156 | le32_to_cpu(ptable->version), tmpparts); |
157 | *pparts = parts; |
158 | |
159 | return numparts; |
160 | |
161 | out_free_parts: |
162 | while (--j >= 0) |
163 | kfree(objp: parts[j].name); |
164 | kfree(objp: parts); |
165 | *pparts = NULL; |
166 | |
167 | return ret; |
168 | } |
169 | |
170 | static void parse_qcomsmem_cleanup(const struct mtd_partition *pparts, |
171 | int nr_parts) |
172 | { |
173 | int i; |
174 | |
175 | for (i = 0; i < nr_parts; i++) |
176 | kfree(objp: pparts[i].name); |
177 | |
178 | kfree(objp: pparts); |
179 | } |
180 | |
181 | static const struct of_device_id qcomsmem_of_match_table[] = { |
182 | { .compatible = "qcom,smem-part" }, |
183 | {}, |
184 | }; |
185 | MODULE_DEVICE_TABLE(of, qcomsmem_of_match_table); |
186 | |
187 | static struct mtd_part_parser mtd_parser_qcomsmem = { |
188 | .parse_fn = parse_qcomsmem_part, |
189 | .cleanup = parse_qcomsmem_cleanup, |
190 | .name = "qcomsmem" , |
191 | .of_match_table = qcomsmem_of_match_table, |
192 | }; |
193 | module_mtd_part_parser(mtd_parser_qcomsmem); |
194 | |
195 | MODULE_LICENSE("GPL v2" ); |
196 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>" ); |
197 | MODULE_DESCRIPTION("Qualcomm SMEM NAND flash partition parser" ); |
198 | |