1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Flash partitions described by the OF (or flattened) device tree |
4 | * |
5 | * Copyright © 2006 MontaVista Software Inc. |
6 | * Author: Vitaly Wool <vwool@ru.mvista.com> |
7 | * |
8 | * Revised to handle newer style flash binding by: |
9 | * Copyright © 2007 David Gibson, IBM Corporation. |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/init.h> |
14 | #include <linux/of.h> |
15 | #include <linux/mtd/mtd.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/mtd/partitions.h> |
18 | |
19 | #include "ofpart_bcm4908.h" |
20 | #include "ofpart_linksys_ns.h" |
21 | |
22 | struct fixed_partitions_quirks { |
23 | int (*post_parse)(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts); |
24 | }; |
25 | |
26 | static struct fixed_partitions_quirks bcm4908_partitions_quirks = { |
27 | .post_parse = bcm4908_partitions_post_parse, |
28 | }; |
29 | |
30 | static struct fixed_partitions_quirks linksys_ns_partitions_quirks = { |
31 | .post_parse = linksys_ns_partitions_post_parse, |
32 | }; |
33 | |
34 | static const struct of_device_id parse_ofpart_match_table[]; |
35 | |
36 | static bool node_has_compatible(struct device_node *pp) |
37 | { |
38 | return of_get_property(node: pp, name: "compatible" , NULL); |
39 | } |
40 | |
41 | static int parse_fixed_partitions(struct mtd_info *master, |
42 | const struct mtd_partition **pparts, |
43 | struct mtd_part_parser_data *data) |
44 | { |
45 | const struct fixed_partitions_quirks *quirks; |
46 | const struct of_device_id *of_id; |
47 | struct mtd_partition *parts; |
48 | struct device_node *mtd_node; |
49 | struct device_node *ofpart_node; |
50 | const char *partname; |
51 | struct device_node *pp; |
52 | int nr_parts, i, ret = 0; |
53 | bool dedicated = true; |
54 | |
55 | /* Pull of_node from the master device node */ |
56 | mtd_node = mtd_get_of_node(mtd: master); |
57 | if (!mtd_node) |
58 | return 0; |
59 | |
60 | if (!master->parent) { /* Master */ |
61 | ofpart_node = of_get_child_by_name(node: mtd_node, name: "partitions" ); |
62 | if (!ofpart_node) { |
63 | /* |
64 | * We might get here even when ofpart isn't used at all (e.g., |
65 | * when using another parser), so don't be louder than |
66 | * KERN_DEBUG |
67 | */ |
68 | pr_debug("%s: 'partitions' subnode not found on %pOF. Trying to parse direct subnodes as partitions.\n" , |
69 | master->name, mtd_node); |
70 | ofpart_node = mtd_node; |
71 | dedicated = false; |
72 | } |
73 | } else { /* Partition */ |
74 | ofpart_node = mtd_node; |
75 | } |
76 | |
77 | of_id = of_match_node(matches: parse_ofpart_match_table, node: ofpart_node); |
78 | if (dedicated && !of_id) { |
79 | /* The 'partitions' subnode might be used by another parser */ |
80 | return 0; |
81 | } |
82 | |
83 | quirks = of_id ? of_id->data : NULL; |
84 | |
85 | /* First count the subnodes */ |
86 | nr_parts = 0; |
87 | for_each_child_of_node(ofpart_node, pp) { |
88 | if (!dedicated && node_has_compatible(pp)) |
89 | continue; |
90 | |
91 | nr_parts++; |
92 | } |
93 | |
94 | if (nr_parts == 0) |
95 | return 0; |
96 | |
97 | parts = kcalloc(n: nr_parts, size: sizeof(*parts), GFP_KERNEL); |
98 | if (!parts) |
99 | return -ENOMEM; |
100 | |
101 | i = 0; |
102 | for_each_child_of_node(ofpart_node, pp) { |
103 | const __be32 *reg; |
104 | int len; |
105 | int a_cells, s_cells; |
106 | |
107 | if (!dedicated && node_has_compatible(pp)) |
108 | continue; |
109 | |
110 | reg = of_get_property(node: pp, name: "reg" , lenp: &len); |
111 | if (!reg) { |
112 | if (dedicated) { |
113 | pr_debug("%s: ofpart partition %pOF (%pOF) missing reg property.\n" , |
114 | master->name, pp, |
115 | mtd_node); |
116 | goto ofpart_fail; |
117 | } else { |
118 | nr_parts--; |
119 | continue; |
120 | } |
121 | } |
122 | |
123 | a_cells = of_n_addr_cells(np: pp); |
124 | s_cells = of_n_size_cells(np: pp); |
125 | if (!dedicated && s_cells == 0) { |
126 | /* |
127 | * This is a ugly workaround to not create |
128 | * regression on devices that are still creating |
129 | * partitions as direct children of the nand controller. |
130 | * This can happen in case the nand controller node has |
131 | * #size-cells equal to 0 and the firmware (e.g. |
132 | * U-Boot) just add the partitions there assuming |
133 | * 32-bit addressing. |
134 | * |
135 | * If you get this warning your firmware and/or DTS |
136 | * should be really fixed. |
137 | * |
138 | * This is working only for devices smaller than 4GiB. |
139 | */ |
140 | pr_warn("%s: ofpart partition %pOF (%pOF) #size-cells is wrongly set to <0>, assuming <1> for parsing partitions.\n" , |
141 | master->name, pp, mtd_node); |
142 | s_cells = 1; |
143 | } |
144 | if (len / 4 != a_cells + s_cells) { |
145 | pr_debug("%s: ofpart partition %pOF (%pOF) error parsing reg property.\n" , |
146 | master->name, pp, |
147 | mtd_node); |
148 | goto ofpart_fail; |
149 | } |
150 | |
151 | parts[i].offset = of_read_number(cell: reg, size: a_cells); |
152 | parts[i].size = of_read_number(cell: reg + a_cells, size: s_cells); |
153 | parts[i].of_node = pp; |
154 | |
155 | partname = of_get_property(node: pp, name: "label" , lenp: &len); |
156 | if (!partname) |
157 | partname = of_get_property(node: pp, name: "name" , lenp: &len); |
158 | parts[i].name = partname; |
159 | |
160 | if (of_get_property(node: pp, name: "read-only" , lenp: &len)) |
161 | parts[i].mask_flags |= MTD_WRITEABLE; |
162 | |
163 | if (of_get_property(node: pp, name: "lock" , lenp: &len)) |
164 | parts[i].mask_flags |= MTD_POWERUP_LOCK; |
165 | |
166 | if (of_property_read_bool(np: pp, propname: "slc-mode" )) |
167 | parts[i].add_flags |= MTD_SLC_ON_MLC_EMULATION; |
168 | |
169 | i++; |
170 | } |
171 | |
172 | if (!nr_parts) |
173 | goto ofpart_none; |
174 | |
175 | if (quirks && quirks->post_parse) |
176 | quirks->post_parse(master, parts, nr_parts); |
177 | |
178 | *pparts = parts; |
179 | return nr_parts; |
180 | |
181 | ofpart_fail: |
182 | pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n" , |
183 | master->name, pp, mtd_node); |
184 | ret = -EINVAL; |
185 | ofpart_none: |
186 | of_node_put(node: pp); |
187 | kfree(objp: parts); |
188 | return ret; |
189 | } |
190 | |
191 | static const struct of_device_id parse_ofpart_match_table[] = { |
192 | /* Generic */ |
193 | { .compatible = "fixed-partitions" }, |
194 | /* Customized */ |
195 | { .compatible = "brcm,bcm4908-partitions" , .data = &bcm4908_partitions_quirks, }, |
196 | { .compatible = "linksys,ns-partitions" , .data = &linksys_ns_partitions_quirks, }, |
197 | {}, |
198 | }; |
199 | MODULE_DEVICE_TABLE(of, parse_ofpart_match_table); |
200 | |
201 | static struct mtd_part_parser ofpart_parser = { |
202 | .parse_fn = parse_fixed_partitions, |
203 | .name = "fixed-partitions" , |
204 | .of_match_table = parse_ofpart_match_table, |
205 | }; |
206 | |
207 | static int parse_ofoldpart_partitions(struct mtd_info *master, |
208 | const struct mtd_partition **pparts, |
209 | struct mtd_part_parser_data *data) |
210 | { |
211 | struct mtd_partition *parts; |
212 | struct device_node *dp; |
213 | int i, plen, nr_parts; |
214 | const struct { |
215 | __be32 offset, len; |
216 | } *part; |
217 | const char *names; |
218 | |
219 | /* Pull of_node from the master device node */ |
220 | dp = mtd_get_of_node(mtd: master); |
221 | if (!dp) |
222 | return 0; |
223 | |
224 | part = of_get_property(node: dp, name: "partitions" , lenp: &plen); |
225 | if (!part) |
226 | return 0; /* No partitions found */ |
227 | |
228 | pr_warn("Device tree uses obsolete partition map binding: %pOF\n" , dp); |
229 | |
230 | nr_parts = plen / sizeof(part[0]); |
231 | |
232 | parts = kcalloc(n: nr_parts, size: sizeof(*parts), GFP_KERNEL); |
233 | if (!parts) |
234 | return -ENOMEM; |
235 | |
236 | names = of_get_property(node: dp, name: "partition-names" , lenp: &plen); |
237 | |
238 | for (i = 0; i < nr_parts; i++) { |
239 | parts[i].offset = be32_to_cpu(part->offset); |
240 | parts[i].size = be32_to_cpu(part->len) & ~1; |
241 | /* bit 0 set signifies read only partition */ |
242 | if (be32_to_cpu(part->len) & 1) |
243 | parts[i].mask_flags = MTD_WRITEABLE; |
244 | |
245 | if (names && (plen > 0)) { |
246 | int len = strlen(names) + 1; |
247 | |
248 | parts[i].name = names; |
249 | plen -= len; |
250 | names += len; |
251 | } else { |
252 | parts[i].name = "unnamed" ; |
253 | } |
254 | |
255 | part++; |
256 | } |
257 | |
258 | *pparts = parts; |
259 | return nr_parts; |
260 | } |
261 | |
262 | static struct mtd_part_parser ofoldpart_parser = { |
263 | .parse_fn = parse_ofoldpart_partitions, |
264 | .name = "ofoldpart" , |
265 | }; |
266 | |
267 | static int __init ofpart_parser_init(void) |
268 | { |
269 | register_mtd_parser(&ofpart_parser); |
270 | register_mtd_parser(&ofoldpart_parser); |
271 | return 0; |
272 | } |
273 | |
274 | static void __exit ofpart_parser_exit(void) |
275 | { |
276 | deregister_mtd_parser(parser: &ofpart_parser); |
277 | deregister_mtd_parser(parser: &ofoldpart_parser); |
278 | } |
279 | |
280 | module_init(ofpart_parser_init); |
281 | module_exit(ofpart_parser_exit); |
282 | |
283 | MODULE_LICENSE("GPL" ); |
284 | MODULE_DESCRIPTION("Parser for MTD partitioning information in device tree" ); |
285 | MODULE_AUTHOR("Vitaly Wool, David Gibson" ); |
286 | /* |
287 | * When MTD core cannot find the requested parser, it tries to load the module |
288 | * with the same name. Since we provide the ofoldpart parser, we should have |
289 | * the corresponding alias. |
290 | */ |
291 | MODULE_ALIAS("fixed-partitions" ); |
292 | MODULE_ALIAS("ofoldpart" ); |
293 | |