GNU Linux-libre 6.8.7-gnu
[releases.git] / drivers / mtd / parsers / scpart.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *    drivers/mtd/scpart.c: Sercomm Partition Parser
4  *
5  *    Copyright (C) 2018 NOGUCHI Hiroshi
6  *    Copyright (C) 2022 Mikhail Zhilkin
7  */
8
9 #include <linux/kernel.h>
10 #include <linux/slab.h>
11 #include <linux/mtd/mtd.h>
12 #include <linux/mtd/partitions.h>
13 #include <linux/module.h>
14
15 #define MOD_NAME        "scpart"
16
17 #ifdef pr_fmt
18 #undef pr_fmt
19 #endif
20
21 #define pr_fmt(fmt) MOD_NAME ": " fmt
22
23 #define ID_ALREADY_FOUND        0xffffffffUL
24
25 #define MAP_OFFS_IN_BLK         0x800
26 #define MAP_MIRROR_NUM          2
27
28 static const char sc_part_magic[] = {
29         'S', 'C', 'F', 'L', 'M', 'A', 'P', 'O', 'K', '\0',
30 };
31 #define PART_MAGIC_LEN          sizeof(sc_part_magic)
32
33 /* assumes that all fields are set by CPU native endian */
34 struct sc_part_desc {
35         uint32_t        part_id;
36         uint32_t        part_offs;
37         uint32_t        part_bytes;
38 };
39
40 static uint32_t scpart_desc_is_valid(struct sc_part_desc *pdesc)
41 {
42         return ((pdesc->part_id != 0xffffffffUL) &&
43                 (pdesc->part_offs != 0xffffffffUL) &&
44                 (pdesc->part_bytes != 0xffffffffUL));
45 }
46
47 static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
48                                struct sc_part_desc **ppdesc)
49 {
50         int cnt = 0;
51         int res = 0;
52         int res2;
53         uint32_t offs;
54         size_t retlen;
55         struct sc_part_desc *pdesc = NULL;
56         struct sc_part_desc *tmpdesc;
57         uint8_t *buf;
58
59         buf = kzalloc(master->erasesize, GFP_KERNEL);
60         if (!buf) {
61                 res = -ENOMEM;
62                 goto out;
63         }
64
65         res2 = mtd_read(master, partmap_offs, master->erasesize, &retlen, buf);
66         if (res2 || retlen != master->erasesize) {
67                 res = -EIO;
68                 goto free;
69         }
70
71         for (offs = MAP_OFFS_IN_BLK;
72              offs < master->erasesize - sizeof(*tmpdesc);
73              offs += sizeof(*tmpdesc)) {
74                 tmpdesc = (struct sc_part_desc *)&buf[offs];
75                 if (!scpart_desc_is_valid(tmpdesc))
76                         break;
77                 cnt++;
78         }
79
80         if (cnt > 0) {
81                 int bytes = cnt * sizeof(*pdesc);
82
83                 pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
84                 if (!pdesc) {
85                         res = -ENOMEM;
86                         goto free;
87                 }
88                 memcpy(pdesc, &(buf[MAP_OFFS_IN_BLK]), bytes);
89
90                 *ppdesc = pdesc;
91                 res = cnt;
92         }
93
94 free:
95         kfree(buf);
96
97 out:
98         return res;
99 }
100
101 static int scpart_find_partmap(struct mtd_info *master,
102                                struct sc_part_desc **ppdesc)
103 {
104         int magic_found = 0;
105         int res = 0;
106         int res2;
107         loff_t offs = 0;
108         size_t retlen;
109         uint8_t rdbuf[PART_MAGIC_LEN];
110
111         while ((magic_found < MAP_MIRROR_NUM) &&
112                         (offs < master->size) &&
113                          !mtd_block_isbad(master, offs)) {
114                 res2 = mtd_read(master, offs, PART_MAGIC_LEN, &retlen, rdbuf);
115                 if (res2 || retlen != PART_MAGIC_LEN) {
116                         res = -EIO;
117                         goto out;
118                 }
119                 if (!memcmp(rdbuf, sc_part_magic, PART_MAGIC_LEN)) {
120                         pr_debug("Signature found at 0x%llx\n", offs);
121                         magic_found++;
122                         res = scpart_scan_partmap(master, offs, ppdesc);
123                         if (res > 0)
124                                 goto out;
125                 }
126                 offs += master->erasesize;
127         }
128
129 out:
130         if (res > 0)
131                 pr_info("Valid 'SC PART MAP' (%d partitions) found at 0x%llx\n", res, offs);
132         else
133                 pr_info("No valid 'SC PART MAP' was found\n");
134
135         return res;
136 }
137
138 static int scpart_parse(struct mtd_info *master,
139                         const struct mtd_partition **pparts,
140                         struct mtd_part_parser_data *data)
141 {
142         const char *partname;
143         int n;
144         int nr_scparts;
145         int nr_parts = 0;
146         int res = 0;
147         struct sc_part_desc *scpart_map = NULL;
148         struct mtd_partition *parts = NULL;
149         struct device_node *mtd_node;
150         struct device_node *ofpart_node;
151         struct device_node *pp;
152
153         mtd_node = mtd_get_of_node(master);
154         if (!mtd_node) {
155                 res = -ENOENT;
156                 goto out;
157         }
158
159         ofpart_node = of_get_child_by_name(mtd_node, "partitions");
160         if (!ofpart_node) {
161                 pr_info("%s: 'partitions' subnode not found on %pOF.\n",
162                                 master->name, mtd_node);
163                 res = -ENOENT;
164                 goto out;
165         }
166
167         nr_scparts = scpart_find_partmap(master, &scpart_map);
168         if (nr_scparts <= 0) {
169                 pr_info("No any partitions was found in 'SC PART MAP'.\n");
170                 res = -ENOENT;
171                 goto free;
172         }
173
174         parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
175                 GFP_KERNEL);
176         if (!parts) {
177                 res = -ENOMEM;
178                 goto free;
179         }
180
181         for_each_child_of_node(ofpart_node, pp) {
182                 u32 scpart_id;
183
184                 if (of_property_read_u32(pp, "sercomm,scpart-id", &scpart_id))
185                         continue;
186
187                 for (n = 0 ; n < nr_scparts ; n++)
188                         if ((scpart_map[n].part_id != ID_ALREADY_FOUND) &&
189                                         (scpart_id == scpart_map[n].part_id))
190                                 break;
191                 if (n >= nr_scparts)
192                         /* not match */
193                         continue;
194
195                 /* add the partition found in OF into MTD partition array */
196                 parts[nr_parts].offset = scpart_map[n].part_offs;
197                 parts[nr_parts].size = scpart_map[n].part_bytes;
198                 parts[nr_parts].of_node = pp;
199
200                 if (!of_property_read_string(pp, "label", &partname))
201                         parts[nr_parts].name = partname;
202                 if (of_property_read_bool(pp, "read-only"))
203                         parts[nr_parts].mask_flags |= MTD_WRITEABLE;
204                 if (of_property_read_bool(pp, "lock"))
205                         parts[nr_parts].mask_flags |= MTD_POWERUP_LOCK;
206
207                 /* mark as 'done' */
208                 scpart_map[n].part_id = ID_ALREADY_FOUND;
209
210                 nr_parts++;
211         }
212
213         if (nr_parts > 0) {
214                 *pparts = parts;
215                 res = nr_parts;
216         } else
217                 pr_info("No partition in OF matches partition ID with 'SC PART MAP'.\n");
218
219         of_node_put(pp);
220
221 free:
222         of_node_put(ofpart_node);
223         kfree(scpart_map);
224         if (res <= 0)
225                 kfree(parts);
226
227 out:
228         return res;
229 }
230
231 static const struct of_device_id scpart_parser_of_match_table[] = {
232         { .compatible = "sercomm,sc-partitions" },
233         {},
234 };
235 MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
236
237 static struct mtd_part_parser scpart_parser = {
238         .parse_fn = scpart_parse,
239         .name = "scpart",
240         .of_match_table = scpart_parser_of_match_table,
241 };
242 module_mtd_part_parser(scpart_parser);
243
244 /* mtd parsers will request the module by parser name */
245 MODULE_ALIAS("scpart");
246 MODULE_LICENSE("GPL");
247 MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
248 MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
249 MODULE_DESCRIPTION("Sercomm partition parser");