1 | // SPDX-License-Identifier: GPL-2.0-or-later |
---|---|
2 | /* |
3 | * Simple MTD partitioning layer |
4 | * |
5 | * Copyright © 2000 Nicolas Pitre <nico@fluxnic.net> |
6 | * Copyright © 2002 Thomas Gleixner <gleixner@linutronix.de> |
7 | * Copyright © 2000-2010 David Woodhouse <dwmw2@infradead.org> |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/types.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/list.h> |
15 | #include <linux/kmod.h> |
16 | #include <linux/mtd/mtd.h> |
17 | #include <linux/mtd/partitions.h> |
18 | #include <linux/err.h> |
19 | #include <linux/of.h> |
20 | #include <linux/of_platform.h> |
21 | |
22 | #include "mtdcore.h" |
23 | |
24 | /* |
25 | * MTD methods which simply translate the effective address and pass through |
26 | * to the _real_ device. |
27 | */ |
28 | |
29 | static inline void free_partition(struct mtd_info *mtd) |
30 | { |
31 | kfree(objp: mtd->name); |
32 | kfree(objp: mtd); |
33 | } |
34 | |
35 | void release_mtd_partition(struct mtd_info *mtd) |
36 | { |
37 | WARN_ON(!list_empty(&mtd->part.node)); |
38 | free_partition(mtd); |
39 | } |
40 | |
41 | static struct mtd_info *allocate_partition(struct mtd_info *parent, |
42 | const struct mtd_partition *part, |
43 | int partno, uint64_t cur_offset) |
44 | { |
45 | struct mtd_info *master = mtd_get_master(mtd: parent); |
46 | int wr_alignment = (parent->flags & MTD_NO_ERASE) ? |
47 | master->writesize : master->erasesize; |
48 | u64 parent_size = mtd_is_partition(mtd: parent) ? |
49 | parent->part.size : parent->size; |
50 | struct mtd_info *child; |
51 | u32 remainder; |
52 | char *name; |
53 | u64 tmp; |
54 | |
55 | /* allocate the partition structure */ |
56 | child = kzalloc(sizeof(*child), GFP_KERNEL); |
57 | name = kstrdup(s: part->name, GFP_KERNEL); |
58 | if (!name || !child) { |
59 | printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n", |
60 | parent->name); |
61 | kfree(objp: name); |
62 | kfree(objp: child); |
63 | return ERR_PTR(error: -ENOMEM); |
64 | } |
65 | |
66 | /* set up the MTD object for this partition */ |
67 | child->type = parent->type; |
68 | child->part.flags = parent->flags & ~part->mask_flags; |
69 | child->part.flags |= part->add_flags; |
70 | child->flags = child->part.flags; |
71 | child->part.size = part->size; |
72 | child->writesize = parent->writesize; |
73 | child->writebufsize = parent->writebufsize; |
74 | child->oobsize = parent->oobsize; |
75 | child->oobavail = parent->oobavail; |
76 | child->subpage_sft = parent->subpage_sft; |
77 | |
78 | child->name = name; |
79 | child->owner = parent->owner; |
80 | |
81 | /* NOTE: Historically, we didn't arrange MTDs as a tree out of |
82 | * concern for showing the same data in multiple partitions. |
83 | * However, it is very useful to have the master node present, |
84 | * so the MTD_PARTITIONED_MASTER option allows that. The master |
85 | * will have device nodes etc only if this is set, so make the |
86 | * parent conditional on that option. Note, this is a way to |
87 | * distinguish between the parent and its partitions in sysfs. |
88 | */ |
89 | child->dev.parent = &parent->dev; |
90 | child->dev.of_node = part->of_node; |
91 | child->parent = parent; |
92 | child->part.offset = part->offset; |
93 | INIT_LIST_HEAD(list: &child->partitions); |
94 | |
95 | if (child->part.offset == MTDPART_OFS_APPEND) |
96 | child->part.offset = cur_offset; |
97 | if (child->part.offset == MTDPART_OFS_NXTBLK) { |
98 | tmp = cur_offset; |
99 | child->part.offset = cur_offset; |
100 | remainder = do_div(tmp, wr_alignment); |
101 | if (remainder) { |
102 | child->part.offset += wr_alignment - remainder; |
103 | printk(KERN_NOTICE "Moving partition %d: " |
104 | "0x%012llx -> 0x%012llx\n", partno, |
105 | (unsigned long long)cur_offset, |
106 | child->part.offset); |
107 | } |
108 | } |
109 | if (child->part.offset == MTDPART_OFS_RETAIN) { |
110 | child->part.offset = cur_offset; |
111 | if (parent_size - child->part.offset >= child->part.size) { |
112 | child->part.size = parent_size - child->part.offset - |
113 | child->part.size; |
114 | } else { |
115 | printk(KERN_ERR "mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n", |
116 | part->name, parent_size - child->part.offset, |
117 | child->part.size); |
118 | /* register to preserve ordering */ |
119 | goto out_register; |
120 | } |
121 | } |
122 | if (child->part.size == MTDPART_SIZ_FULL) |
123 | child->part.size = parent_size - child->part.offset; |
124 | |
125 | printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n", |
126 | child->part.offset, child->part.offset + child->part.size, |
127 | child->name); |
128 | |
129 | /* let's do some sanity checks */ |
130 | if (child->part.offset >= parent_size) { |
131 | /* let's register it anyway to preserve ordering */ |
132 | child->part.offset = 0; |
133 | child->part.size = 0; |
134 | |
135 | /* Initialize ->erasesize to make add_mtd_device() happy. */ |
136 | child->erasesize = parent->erasesize; |
137 | printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n", |
138 | part->name); |
139 | goto out_register; |
140 | } |
141 | if (child->part.offset + child->part.size > parent->size) { |
142 | child->part.size = parent_size - child->part.offset; |
143 | printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n", |
144 | part->name, parent->name, child->part.size); |
145 | } |
146 | |
147 | if (parent->numeraseregions > 1) { |
148 | /* Deal with variable erase size stuff */ |
149 | int i, max = parent->numeraseregions; |
150 | u64 end = child->part.offset + child->part.size; |
151 | struct mtd_erase_region_info *regions = parent->eraseregions; |
152 | |
153 | /* Find the first erase regions which is part of this |
154 | * partition. */ |
155 | for (i = 0; i < max && regions[i].offset <= child->part.offset; |
156 | i++) |
157 | ; |
158 | /* The loop searched for the region _behind_ the first one */ |
159 | if (i > 0) |
160 | i--; |
161 | |
162 | /* Pick biggest erasesize */ |
163 | for (; i < max && regions[i].offset < end; i++) { |
164 | if (child->erasesize < regions[i].erasesize) |
165 | child->erasesize = regions[i].erasesize; |
166 | } |
167 | BUG_ON(child->erasesize == 0); |
168 | } else { |
169 | /* Single erase size */ |
170 | child->erasesize = master->erasesize; |
171 | } |
172 | |
173 | /* |
174 | * Child erasesize might differ from the parent one if the parent |
175 | * exposes several regions with different erasesize. Adjust |
176 | * wr_alignment accordingly. |
177 | */ |
178 | if (!(child->flags & MTD_NO_ERASE)) |
179 | wr_alignment = child->erasesize; |
180 | |
181 | tmp = mtd_get_master_ofs(mtd: child, ofs: 0); |
182 | remainder = do_div(tmp, wr_alignment); |
183 | if ((child->flags & MTD_WRITEABLE) && remainder) { |
184 | /* Doesn't start on a boundary of major erase size */ |
185 | /* FIXME: Let it be writable if it is on a boundary of |
186 | * _minor_ erase size though */ |
187 | child->flags &= ~MTD_WRITEABLE; |
188 | printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n", |
189 | part->name); |
190 | } |
191 | |
192 | tmp = mtd_get_master_ofs(mtd: child, ofs: 0) + child->part.size; |
193 | remainder = do_div(tmp, wr_alignment); |
194 | if ((child->flags & MTD_WRITEABLE) && remainder) { |
195 | child->flags &= ~MTD_WRITEABLE; |
196 | printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n", |
197 | part->name); |
198 | } |
199 | |
200 | child->size = child->part.size; |
201 | child->ecc_step_size = parent->ecc_step_size; |
202 | child->ecc_strength = parent->ecc_strength; |
203 | child->bitflip_threshold = parent->bitflip_threshold; |
204 | |
205 | if (master->_block_isbad) { |
206 | uint64_t offs = 0; |
207 | |
208 | while (offs < child->part.size) { |
209 | if (mtd_block_isreserved(mtd: child, ofs: offs)) |
210 | child->ecc_stats.bbtblocks++; |
211 | else if (mtd_block_isbad(mtd: child, ofs: offs)) |
212 | child->ecc_stats.badblocks++; |
213 | offs += child->erasesize; |
214 | } |
215 | } |
216 | |
217 | out_register: |
218 | return child; |
219 | } |
220 | |
221 | static ssize_t offset_show(struct device *dev, |
222 | struct device_attribute *attr, char *buf) |
223 | { |
224 | struct mtd_info *mtd = dev_get_drvdata(dev); |
225 | |
226 | return sysfs_emit(buf, fmt: "%lld\n", mtd->part.offset); |
227 | } |
228 | static DEVICE_ATTR_RO(offset); /* mtd partition offset */ |
229 | |
230 | static const struct attribute *mtd_partition_attrs[] = { |
231 | &dev_attr_offset.attr, |
232 | NULL |
233 | }; |
234 | |
235 | static int mtd_add_partition_attrs(struct mtd_info *new) |
236 | { |
237 | int ret = sysfs_create_files(kobj: &new->dev.kobj, attr: mtd_partition_attrs); |
238 | if (ret) |
239 | printk(KERN_WARNING |
240 | "mtd: failed to create partition attrs, err=%d\n", ret); |
241 | return ret; |
242 | } |
243 | |
244 | int mtd_add_partition(struct mtd_info *parent, const char *name, |
245 | long long offset, long long length, struct mtd_info **out) |
246 | { |
247 | struct mtd_info *master = mtd_get_master(mtd: parent); |
248 | u64 parent_size = mtd_is_partition(mtd: parent) ? |
249 | parent->part.size : parent->size; |
250 | struct mtd_partition part; |
251 | struct mtd_info *child; |
252 | int ret = 0; |
253 | |
254 | /* the direct offset is expected */ |
255 | if (offset == MTDPART_OFS_APPEND || |
256 | offset == MTDPART_OFS_NXTBLK) |
257 | return -EINVAL; |
258 | |
259 | if (length == MTDPART_SIZ_FULL) |
260 | length = parent_size - offset; |
261 | |
262 | if (length <= 0) |
263 | return -EINVAL; |
264 | |
265 | memset(&part, 0, sizeof(part)); |
266 | part.name = name; |
267 | part.size = length; |
268 | part.offset = offset; |
269 | |
270 | child = allocate_partition(parent, part: &part, partno: -1, cur_offset: offset); |
271 | if (IS_ERR(ptr: child)) |
272 | return PTR_ERR(ptr: child); |
273 | |
274 | mutex_lock(&master->master.partitions_lock); |
275 | list_add_tail(new: &child->part.node, head: &parent->partitions); |
276 | mutex_unlock(lock: &master->master.partitions_lock); |
277 | |
278 | ret = add_mtd_device(mtd: child, partitioned: true); |
279 | if (ret) |
280 | goto err_remove_part; |
281 | |
282 | mtd_add_partition_attrs(new: child); |
283 | |
284 | if (out) |
285 | *out = child; |
286 | |
287 | return 0; |
288 | |
289 | err_remove_part: |
290 | mutex_lock(&master->master.partitions_lock); |
291 | list_del(entry: &child->part.node); |
292 | mutex_unlock(lock: &master->master.partitions_lock); |
293 | |
294 | free_partition(mtd: child); |
295 | |
296 | return ret; |
297 | } |
298 | EXPORT_SYMBOL_GPL(mtd_add_partition); |
299 | |
300 | /** |
301 | * __mtd_del_partition - delete MTD partition |
302 | * |
303 | * @mtd: MTD structure to be deleted |
304 | * |
305 | * This function must be called with the partitions mutex locked. |
306 | */ |
307 | static int __mtd_del_partition(struct mtd_info *mtd) |
308 | { |
309 | struct mtd_info *child, *next; |
310 | int err; |
311 | |
312 | list_for_each_entry_safe(child, next, &mtd->partitions, part.node) { |
313 | err = __mtd_del_partition(mtd: child); |
314 | if (err) |
315 | return err; |
316 | } |
317 | |
318 | sysfs_remove_files(kobj: &mtd->dev.kobj, attr: mtd_partition_attrs); |
319 | |
320 | list_del_init(entry: &mtd->part.node); |
321 | err = del_mtd_device(mtd); |
322 | if (err) |
323 | return err; |
324 | |
325 | return 0; |
326 | } |
327 | |
328 | /* |
329 | * This function unregisters and destroy all slave MTD objects which are |
330 | * attached to the given MTD object, recursively. |
331 | */ |
332 | static int __del_mtd_partitions(struct mtd_info *mtd) |
333 | { |
334 | struct mtd_info *child, *next; |
335 | int ret, err = 0; |
336 | |
337 | list_for_each_entry_safe(child, next, &mtd->partitions, part.node) { |
338 | if (mtd_has_partitions(mtd: child)) |
339 | __del_mtd_partitions(mtd: child); |
340 | |
341 | pr_info("Deleting %s MTD partition\n", child->name); |
342 | list_del_init(entry: &child->part.node); |
343 | ret = del_mtd_device(mtd: child); |
344 | if (ret < 0) { |
345 | pr_err("Error when deleting partition \"%s\" (%d)\n", |
346 | child->name, ret); |
347 | err = ret; |
348 | continue; |
349 | } |
350 | } |
351 | |
352 | return err; |
353 | } |
354 | |
355 | int del_mtd_partitions(struct mtd_info *mtd) |
356 | { |
357 | struct mtd_info *master = mtd_get_master(mtd); |
358 | int ret; |
359 | |
360 | pr_info("Deleting MTD partitions on \"%s\":\n", mtd->name); |
361 | |
362 | mutex_lock(&master->master.partitions_lock); |
363 | ret = __del_mtd_partitions(mtd); |
364 | mutex_unlock(lock: &master->master.partitions_lock); |
365 | |
366 | return ret; |
367 | } |
368 | |
369 | int mtd_del_partition(struct mtd_info *mtd, int partno) |
370 | { |
371 | struct mtd_info *child, *master = mtd_get_master(mtd); |
372 | int ret = -EINVAL; |
373 | |
374 | mutex_lock(&master->master.partitions_lock); |
375 | list_for_each_entry(child, &mtd->partitions, part.node) { |
376 | if (child->index == partno) { |
377 | ret = __mtd_del_partition(mtd: child); |
378 | break; |
379 | } |
380 | } |
381 | mutex_unlock(lock: &master->master.partitions_lock); |
382 | |
383 | return ret; |
384 | } |
385 | EXPORT_SYMBOL_GPL(mtd_del_partition); |
386 | |
387 | /* |
388 | * This function, given a parent MTD object and a partition table, creates |
389 | * and registers the child MTD objects which are bound to the parent according |
390 | * to the partition definitions. |
391 | * |
392 | * For historical reasons, this function's caller only registers the parent |
393 | * if the MTD_PARTITIONED_MASTER config option is set. |
394 | */ |
395 | |
396 | int add_mtd_partitions(struct mtd_info *parent, |
397 | const struct mtd_partition *parts, |
398 | int nbparts) |
399 | { |
400 | struct mtd_info *child, *master = mtd_get_master(mtd: parent); |
401 | uint64_t cur_offset = 0; |
402 | int i, ret; |
403 | |
404 | printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", |
405 | nbparts, parent->name); |
406 | |
407 | for (i = 0; i < nbparts; i++) { |
408 | child = allocate_partition(parent, part: parts + i, partno: i, cur_offset); |
409 | if (IS_ERR(ptr: child)) { |
410 | ret = PTR_ERR(ptr: child); |
411 | goto err_del_partitions; |
412 | } |
413 | |
414 | mutex_lock(&master->master.partitions_lock); |
415 | list_add_tail(new: &child->part.node, head: &parent->partitions); |
416 | mutex_unlock(lock: &master->master.partitions_lock); |
417 | |
418 | ret = add_mtd_device(mtd: child, partitioned: true); |
419 | if (ret) { |
420 | mutex_lock(&master->master.partitions_lock); |
421 | list_del(entry: &child->part.node); |
422 | mutex_unlock(lock: &master->master.partitions_lock); |
423 | |
424 | free_partition(mtd: child); |
425 | goto err_del_partitions; |
426 | } |
427 | |
428 | mtd_add_partition_attrs(new: child); |
429 | |
430 | /* Look for subpartitions */ |
431 | ret = parse_mtd_partitions(master: child, types: parts[i].types, NULL); |
432 | if (ret < 0) { |
433 | pr_err("Failed to parse subpartitions: %d\n", ret); |
434 | goto err_del_partitions; |
435 | } |
436 | |
437 | cur_offset = child->part.offset + child->part.size; |
438 | } |
439 | |
440 | return 0; |
441 | |
442 | err_del_partitions: |
443 | del_mtd_partitions(mtd: master); |
444 | |
445 | return ret; |
446 | } |
447 | |
448 | static DEFINE_SPINLOCK(part_parser_lock); |
449 | static LIST_HEAD(part_parsers); |
450 | |
451 | static struct mtd_part_parser *mtd_part_parser_get(const char *name) |
452 | { |
453 | struct mtd_part_parser *p, *ret = NULL; |
454 | |
455 | spin_lock(lock: &part_parser_lock); |
456 | |
457 | list_for_each_entry(p, &part_parsers, list) |
458 | if (!strcmp(p->name, name) && try_module_get(module: p->owner)) { |
459 | ret = p; |
460 | break; |
461 | } |
462 | |
463 | spin_unlock(lock: &part_parser_lock); |
464 | |
465 | return ret; |
466 | } |
467 | |
468 | static inline void mtd_part_parser_put(const struct mtd_part_parser *p) |
469 | { |
470 | module_put(module: p->owner); |
471 | } |
472 | |
473 | /* |
474 | * Many partition parsers just expected the core to kfree() all their data in |
475 | * one chunk. Do that by default. |
476 | */ |
477 | static void mtd_part_parser_cleanup_default(const struct mtd_partition *pparts, |
478 | int nr_parts) |
479 | { |
480 | kfree(objp: pparts); |
481 | } |
482 | |
483 | int __register_mtd_parser(struct mtd_part_parser *p, struct module *owner) |
484 | { |
485 | p->owner = owner; |
486 | |
487 | if (!p->cleanup) |
488 | p->cleanup = &mtd_part_parser_cleanup_default; |
489 | |
490 | spin_lock(lock: &part_parser_lock); |
491 | list_add(new: &p->list, head: &part_parsers); |
492 | spin_unlock(lock: &part_parser_lock); |
493 | |
494 | return 0; |
495 | } |
496 | EXPORT_SYMBOL_GPL(__register_mtd_parser); |
497 | |
498 | void deregister_mtd_parser(struct mtd_part_parser *p) |
499 | { |
500 | spin_lock(lock: &part_parser_lock); |
501 | list_del(entry: &p->list); |
502 | spin_unlock(lock: &part_parser_lock); |
503 | } |
504 | EXPORT_SYMBOL_GPL(deregister_mtd_parser); |
505 | |
506 | /* |
507 | * Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you |
508 | * are changing this array! |
509 | */ |
510 | static const char * const default_mtd_part_types[] = { |
511 | "cmdlinepart", |
512 | "ofpart", |
513 | NULL |
514 | }; |
515 | |
516 | /* Check DT only when looking for subpartitions. */ |
517 | static const char * const default_subpartition_types[] = { |
518 | "ofpart", |
519 | NULL |
520 | }; |
521 | |
522 | static int mtd_part_do_parse(struct mtd_part_parser *parser, |
523 | struct mtd_info *master, |
524 | struct mtd_partitions *pparts, |
525 | struct mtd_part_parser_data *data) |
526 | { |
527 | int ret; |
528 | |
529 | ret = (*parser->parse_fn)(master, &pparts->parts, data); |
530 | pr_debug("%s: parser %s: %i\n", master->name, parser->name, ret); |
531 | if (ret <= 0) |
532 | return ret; |
533 | |
534 | pr_notice("%d %s partitions found on MTD device %s\n", ret, |
535 | parser->name, master->name); |
536 | |
537 | pparts->nr_parts = ret; |
538 | pparts->parser = parser; |
539 | |
540 | return ret; |
541 | } |
542 | |
543 | /** |
544 | * mtd_part_get_compatible_parser - find MTD parser by a compatible string |
545 | * |
546 | * @compat: compatible string describing partitions in a device tree |
547 | * |
548 | * MTD parsers can specify supported partitions by providing a table of |
549 | * compatibility strings. This function finds a parser that advertises support |
550 | * for a passed value of "compatible". |
551 | */ |
552 | static struct mtd_part_parser *mtd_part_get_compatible_parser(const char *compat) |
553 | { |
554 | struct mtd_part_parser *p, *ret = NULL; |
555 | |
556 | spin_lock(lock: &part_parser_lock); |
557 | |
558 | list_for_each_entry(p, &part_parsers, list) { |
559 | const struct of_device_id *matches; |
560 | |
561 | matches = p->of_match_table; |
562 | if (!matches) |
563 | continue; |
564 | |
565 | for (; matches->compatible[0]; matches++) { |
566 | if (!strcmp(matches->compatible, compat) && |
567 | try_module_get(module: p->owner)) { |
568 | ret = p; |
569 | break; |
570 | } |
571 | } |
572 | |
573 | if (ret) |
574 | break; |
575 | } |
576 | |
577 | spin_unlock(lock: &part_parser_lock); |
578 | |
579 | return ret; |
580 | } |
581 | |
582 | static int mtd_part_of_parse(struct mtd_info *master, |
583 | struct mtd_partitions *pparts) |
584 | { |
585 | struct mtd_part_parser *parser; |
586 | struct device_node *np; |
587 | struct device_node *child; |
588 | struct property *prop; |
589 | struct device *dev; |
590 | const char *compat; |
591 | const char *fixed = "fixed-partitions"; |
592 | int ret, err = 0; |
593 | |
594 | dev = &master->dev; |
595 | |
596 | np = mtd_get_of_node(mtd: master); |
597 | if (mtd_is_partition(mtd: master)) |
598 | of_node_get(node: np); |
599 | else |
600 | np = of_get_child_by_name(node: np, name: "partitions"); |
601 | |
602 | /* |
603 | * Don't create devices that are added to a bus but will never get |
604 | * probed. That'll cause fw_devlink to block probing of consumers of |
605 | * this partition until the partition device is probed. |
606 | */ |
607 | for_each_child_of_node(np, child) |
608 | if (of_device_is_compatible(device: child, "nvmem-cells")) |
609 | of_node_set_flag(n: child, OF_POPULATED); |
610 | |
611 | of_property_for_each_string(np, "compatible", prop, compat) { |
612 | parser = mtd_part_get_compatible_parser(compat); |
613 | if (!parser) |
614 | continue; |
615 | ret = mtd_part_do_parse(parser, master, pparts, NULL); |
616 | if (ret > 0) { |
617 | of_platform_populate(root: np, NULL, NULL, parent: dev); |
618 | of_node_put(node: np); |
619 | return ret; |
620 | } |
621 | mtd_part_parser_put(p: parser); |
622 | if (ret < 0 && !err) |
623 | err = ret; |
624 | } |
625 | of_platform_populate(root: np, NULL, NULL, parent: dev); |
626 | of_node_put(node: np); |
627 | |
628 | /* |
629 | * For backward compatibility we have to try the "fixed-partitions" |
630 | * parser. It supports old DT format with partitions specified as a |
631 | * direct subnodes of a flash device DT node without any compatibility |
632 | * specified we could match. |
633 | */ |
634 | parser = mtd_part_parser_get(name: fixed); |
635 | if (!parser && !request_module("%s", fixed)) |
636 | parser = mtd_part_parser_get(name: fixed); |
637 | if (parser) { |
638 | ret = mtd_part_do_parse(parser, master, pparts, NULL); |
639 | if (ret > 0) |
640 | return ret; |
641 | mtd_part_parser_put(p: parser); |
642 | if (ret < 0 && !err) |
643 | err = ret; |
644 | } |
645 | |
646 | return err; |
647 | } |
648 | |
649 | /** |
650 | * parse_mtd_partitions - parse and register MTD partitions |
651 | * |
652 | * @master: the master partition (describes whole MTD device) |
653 | * @types: names of partition parsers to try or %NULL |
654 | * @data: MTD partition parser-specific data |
655 | * |
656 | * This function tries to find & register partitions on MTD device @master. It |
657 | * uses MTD partition parsers, specified in @types. However, if @types is %NULL, |
658 | * then the default list of parsers is used. The default list contains only the |
659 | * "cmdlinepart" and "ofpart" parsers ATM. |
660 | * Note: If there are more then one parser in @types, the kernel only takes the |
661 | * partitions parsed out by the first parser. |
662 | * |
663 | * This function may return: |
664 | * o a negative error code in case of failure |
665 | * o number of found partitions otherwise |
666 | */ |
667 | int parse_mtd_partitions(struct mtd_info *master, const char *const *types, |
668 | struct mtd_part_parser_data *data) |
669 | { |
670 | struct mtd_partitions pparts = { }; |
671 | struct mtd_part_parser *parser; |
672 | int ret, err = 0; |
673 | |
674 | if (!types) |
675 | types = mtd_is_partition(mtd: master) ? default_subpartition_types : |
676 | default_mtd_part_types; |
677 | |
678 | for ( ; *types; types++) { |
679 | /* |
680 | * ofpart is a special type that means OF partitioning info |
681 | * should be used. It requires a bit different logic so it is |
682 | * handled in a separated function. |
683 | */ |
684 | if (!strcmp(*types, "ofpart")) { |
685 | ret = mtd_part_of_parse(master, pparts: &pparts); |
686 | } else { |
687 | pr_debug("%s: parsing partitions %s\n", master->name, |
688 | *types); |
689 | parser = mtd_part_parser_get(name: *types); |
690 | if (!parser && !request_module("%s", *types)) |
691 | parser = mtd_part_parser_get(name: *types); |
692 | if (!parser) |
693 | continue; |
694 | pr_debug("%s: got parser %s\n", master->name, parser->name); |
695 | ret = mtd_part_do_parse(parser, master, pparts: &pparts, data); |
696 | if (ret <= 0) |
697 | mtd_part_parser_put(p: parser); |
698 | } |
699 | /* Found partitions! */ |
700 | if (ret > 0) { |
701 | err = add_mtd_partitions(parent: master, parts: pparts.parts, |
702 | nbparts: pparts.nr_parts); |
703 | mtd_part_parser_cleanup(parts: &pparts); |
704 | return err ? err : pparts.nr_parts; |
705 | } |
706 | /* |
707 | * Stash the first error we see; only report it if no parser |
708 | * succeeds |
709 | */ |
710 | if (ret < 0 && !err) |
711 | err = ret; |
712 | } |
713 | |
714 | return err; |
715 | } |
716 | |
717 | void mtd_part_parser_cleanup(struct mtd_partitions *parts) |
718 | { |
719 | const struct mtd_part_parser *parser; |
720 | |
721 | if (!parts) |
722 | return; |
723 | |
724 | parser = parts->parser; |
725 | if (parser) { |
726 | if (parser->cleanup) |
727 | parser->cleanup(parts->parts, parts->nr_parts); |
728 | |
729 | mtd_part_parser_put(p: parser); |
730 | } |
731 | } |
732 | |
733 | /* Returns the size of the entire flash chip */ |
734 | uint64_t mtd_get_device_size(const struct mtd_info *mtd) |
735 | { |
736 | struct mtd_info *master = mtd_get_master(mtd: (struct mtd_info *)mtd); |
737 | |
738 | return master->size; |
739 | } |
740 | EXPORT_SYMBOL_GPL(mtd_get_device_size); |
741 |
Definitions
- free_partition
- release_mtd_partition
- allocate_partition
- offset_show
- mtd_partition_attrs
- mtd_add_partition_attrs
- mtd_add_partition
- __mtd_del_partition
- __del_mtd_partitions
- del_mtd_partitions
- mtd_del_partition
- add_mtd_partitions
- part_parser_lock
- part_parsers
- mtd_part_parser_get
- mtd_part_parser_put
- mtd_part_parser_cleanup_default
- __register_mtd_parser
- deregister_mtd_parser
- default_mtd_part_types
- default_subpartition_types
- mtd_part_do_parse
- mtd_part_get_compatible_parser
- mtd_part_of_parse
- parse_mtd_partitions
- mtd_part_parser_cleanup
Improve your Profiling and Debugging skills
Find out more