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
28static 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 */
34struct sc_part_desc {
35 uint32_t part_id;
36 uint32_t part_offs;
37 uint32_t part_bytes;
38};
39
40static 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
47static 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(size: master->erasesize, GFP_KERNEL);
60 if (!buf) {
61 res = -ENOMEM;
62 goto out;
63 }
64
65 res2 = mtd_read(mtd: master, from: partmap_offs, len: master->erasesize, retlen: &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(pdesc: tmpdesc))
76 break;
77 cnt++;
78 }
79
80 if (cnt > 0) {
81 int bytes = cnt * sizeof(*pdesc);
82
83 pdesc = kcalloc(n: cnt, size: 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
94free:
95 kfree(objp: buf);
96
97out:
98 return res;
99}
100
101static 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(mtd: master, ofs: offs)) {
114 res2 = mtd_read(mtd: master, from: offs, PART_MAGIC_LEN, retlen: &retlen, buf: rdbuf);
115 if (res2 || retlen != PART_MAGIC_LEN) {
116 res = -EIO;
117 goto out;
118 }
119 if (!memcmp(p: rdbuf, q: 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, partmap_offs: offs, ppdesc);
123 if (res > 0)
124 goto out;
125 }
126 offs += master->erasesize;
127 }
128
129out:
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
138static 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(mtd: master);
154 if (!mtd_node) {
155 res = -ENOENT;
156 goto out;
157 }
158
159 ofpart_node = of_get_child_by_name(node: mtd_node, name: "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, ppdesc: &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(n: of_get_child_count(np: ofpart_node), size: 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(np: pp, propname: "sercomm,scpart-id", out_value: &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(np: pp, propname: "label", out_string: &partname))
201 parts[nr_parts].name = partname;
202 if (of_property_read_bool(np: pp, propname: "read-only"))
203 parts[nr_parts].mask_flags |= MTD_WRITEABLE;
204 if (of_property_read_bool(np: pp, propname: "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(node: pp);
220
221free:
222 of_node_put(node: ofpart_node);
223 kfree(objp: scpart_map);
224 if (res <= 0)
225 kfree(objp: parts);
226
227out:
228 return res;
229}
230
231static const struct of_device_id scpart_parser_of_match_table[] = {
232 { .compatible = "sercomm,sc-partitions" },
233 {},
234};
235MODULE_DEVICE_TABLE(of, scpart_parser_of_match_table);
236
237static struct mtd_part_parser scpart_parser = {
238 .parse_fn = scpart_parse,
239 .name = "scpart",
240 .of_match_table = scpart_parser_of_match_table,
241};
242module_mtd_part_parser(scpart_parser);
243
244/* mtd parsers will request the module by parser name */
245MODULE_ALIAS("scpart");
246MODULE_LICENSE("GPL");
247MODULE_AUTHOR("NOGUCHI Hiroshi <drvlabo@gmail.com>");
248MODULE_AUTHOR("Mikhail Zhilkin <csharper2005@gmail.com>");
249MODULE_DESCRIPTION("Sercomm partition parser");
250

source code of linux/drivers/mtd/parsers/scpart.c