Commit 5db66b3aee6f2c057706d8715f7e5c472e82f047
cmd: mtd: add 'mtd' command
There should not be a 'nand' command, a 'sf' command and certainly not a new 'spi-nand' command. Write a 'mtd' command instead to manage all MTD devices/partitions at once. This should be the preferred way to access any MTD device. Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com> Acked-by: Jagan Teki <jagan@openedev.com> Reviewed-by: Stefan Roese <sr@denx.de> Reviewed-by: Boris Brezillon <boris.brezillon@bootlin.com>
Showing 6 changed files with 644 additions and 4 deletions Side-by-side Diff
... | ... | @@ -864,6 +864,12 @@ |
864 | 864 | Enable support for the "mmc swrite" command to write Android sparse |
865 | 865 | images to eMMC. |
866 | 866 | |
867 | +config CMD_MTD | |
868 | + bool "mtd" | |
869 | + select MTD_PARTITIONS | |
870 | + help | |
871 | + MTD commands support. | |
872 | + | |
867 | 873 | config CMD_NAND |
868 | 874 | bool "nand" |
869 | 875 | default y if NAND_SUNXI |
870 | 876 | |
... | ... | @@ -1697,14 +1703,14 @@ |
1697 | 1703 | |
1698 | 1704 | config MTDIDS_DEFAULT |
1699 | 1705 | string "Default MTD IDs" |
1700 | - depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH | |
1706 | + depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH | |
1701 | 1707 | help |
1702 | 1708 | Defines a default MTD IDs list for use with MTD partitions in the |
1703 | 1709 | Linux MTD command line partitions format. |
1704 | 1710 | |
1705 | 1711 | config MTDPARTS_DEFAULT |
1706 | 1712 | string "Default MTD partition scheme" |
1707 | - depends on CMD_MTDPARTS || CMD_NAND || CMD_FLASH | |
1713 | + depends on CMD_MTD || CMD_MTDPARTS || CMD_NAND || CMD_FLASH | |
1708 | 1714 | help |
1709 | 1715 | Defines a default MTD partitioning scheme in the Linux MTD command |
1710 | 1716 | line partitions format |
1 | +// SPDX-License-Identifier: GPL-2.0+ | |
2 | +/* | |
3 | + * mtd.c | |
4 | + * | |
5 | + * Generic command to handle basic operations on any memory device. | |
6 | + * | |
7 | + * Copyright: Bootlin, 2018 | |
8 | + * Author: Miquèl Raynal <miquel.raynal@bootlin.com> | |
9 | + */ | |
10 | + | |
11 | +#include <command.h> | |
12 | +#include <common.h> | |
13 | +#include <console.h> | |
14 | +#include <malloc.h> | |
15 | +#include <mapmem.h> | |
16 | +#include <mtd.h> | |
17 | + | |
18 | +static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) | |
19 | +{ | |
20 | + do_div(len, mtd->writesize); | |
21 | + | |
22 | + return len; | |
23 | +} | |
24 | + | |
25 | +static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) | |
26 | +{ | |
27 | + return !do_div(size, mtd->writesize); | |
28 | +} | |
29 | + | |
30 | +static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) | |
31 | +{ | |
32 | + return !do_div(size, mtd->erasesize); | |
33 | +} | |
34 | + | |
35 | +static void mtd_dump_buf(const u8 *buf, uint len, uint offset) | |
36 | +{ | |
37 | + int i, j; | |
38 | + | |
39 | + for (i = 0; i < len; ) { | |
40 | + printf("0x%08x:\t", offset + i); | |
41 | + for (j = 0; j < 8; j++) | |
42 | + printf("%02x ", buf[i + j]); | |
43 | + printf(" "); | |
44 | + i += 8; | |
45 | + for (j = 0; j < 8; j++) | |
46 | + printf("%02x ", buf[i + j]); | |
47 | + printf("\n"); | |
48 | + i += 8; | |
49 | + } | |
50 | +} | |
51 | + | |
52 | +static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, | |
53 | + const u8 *buf, u64 len, bool woob) | |
54 | +{ | |
55 | + bool has_pages = mtd->type == MTD_NANDFLASH || | |
56 | + mtd->type == MTD_MLCNANDFLASH; | |
57 | + int npages = mtd_len_to_pages(mtd, len); | |
58 | + uint page; | |
59 | + | |
60 | + if (has_pages) { | |
61 | + for (page = 0; page < npages; page++) { | |
62 | + u64 data_off = page * mtd->writesize; | |
63 | + | |
64 | + printf("\nDump %d data bytes from 0x%08llx:\n", | |
65 | + mtd->writesize, start_off + data_off); | |
66 | + mtd_dump_buf(&buf[data_off], | |
67 | + mtd->writesize, start_off + data_off); | |
68 | + | |
69 | + if (woob) { | |
70 | + u64 oob_off = page * mtd->oobsize; | |
71 | + | |
72 | + printf("Dump %d OOB bytes from page at 0x%08llx:\n", | |
73 | + mtd->oobsize, start_off + data_off); | |
74 | + mtd_dump_buf(&buf[len + oob_off], | |
75 | + mtd->oobsize, 0); | |
76 | + } | |
77 | + } | |
78 | + } else { | |
79 | + printf("\nDump %lld data bytes from 0x%llx:\n", | |
80 | + len, start_off); | |
81 | + mtd_dump_buf(buf, len, start_off); | |
82 | + } | |
83 | +} | |
84 | + | |
85 | +static void mtd_show_parts(struct mtd_info *mtd, int level) | |
86 | +{ | |
87 | + struct mtd_info *part; | |
88 | + int i; | |
89 | + | |
90 | + list_for_each_entry(part, &mtd->partitions, node) { | |
91 | + for (i = 0; i < level; i++) | |
92 | + printf("\t"); | |
93 | + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
94 | + part->offset, part->offset + part->size, part->name); | |
95 | + | |
96 | + mtd_show_parts(part, level + 1); | |
97 | + } | |
98 | +} | |
99 | + | |
100 | +static void mtd_show_device(struct mtd_info *mtd) | |
101 | +{ | |
102 | + /* Device */ | |
103 | + printf("* %s\n", mtd->name); | |
104 | +#if defined(CONFIG_DM) | |
105 | + if (mtd->dev) { | |
106 | + printf(" - device: %s\n", mtd->dev->name); | |
107 | + printf(" - parent: %s\n", mtd->dev->parent->name); | |
108 | + printf(" - driver: %s\n", mtd->dev->driver->name); | |
109 | + } | |
110 | +#endif | |
111 | + | |
112 | + /* MTD device information */ | |
113 | + printf(" - type: "); | |
114 | + switch (mtd->type) { | |
115 | + case MTD_RAM: | |
116 | + printf("RAM\n"); | |
117 | + break; | |
118 | + case MTD_ROM: | |
119 | + printf("ROM\n"); | |
120 | + break; | |
121 | + case MTD_NORFLASH: | |
122 | + printf("NOR flash\n"); | |
123 | + break; | |
124 | + case MTD_NANDFLASH: | |
125 | + printf("NAND flash\n"); | |
126 | + break; | |
127 | + case MTD_DATAFLASH: | |
128 | + printf("Data flash\n"); | |
129 | + break; | |
130 | + case MTD_UBIVOLUME: | |
131 | + printf("UBI volume\n"); | |
132 | + break; | |
133 | + case MTD_MLCNANDFLASH: | |
134 | + printf("MLC NAND flash\n"); | |
135 | + break; | |
136 | + case MTD_ABSENT: | |
137 | + default: | |
138 | + printf("Unknown\n"); | |
139 | + break; | |
140 | + } | |
141 | + | |
142 | + printf(" - block size: 0x%x bytes\n", mtd->erasesize); | |
143 | + printf(" - min I/O: 0x%x bytes\n", mtd->writesize); | |
144 | + | |
145 | + if (mtd->oobsize) { | |
146 | + printf(" - OOB size: %u bytes\n", mtd->oobsize); | |
147 | + printf(" - OOB available: %u bytes\n", mtd->oobavail); | |
148 | + } | |
149 | + | |
150 | + if (mtd->ecc_strength) { | |
151 | + printf(" - ECC strength: %u bits\n", mtd->ecc_strength); | |
152 | + printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); | |
153 | + printf(" - bitflip threshold: %u bits\n", | |
154 | + mtd->bitflip_threshold); | |
155 | + } | |
156 | + | |
157 | + printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
158 | + mtd->offset, mtd->offset + mtd->size, mtd->name); | |
159 | + | |
160 | + /* MTD partitions, if any */ | |
161 | + mtd_show_parts(mtd, 1); | |
162 | +} | |
163 | + | |
164 | +/* Logic taken from fs/ubifs/recovery.c:is_empty() */ | |
165 | +static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) | |
166 | +{ | |
167 | + int i; | |
168 | + | |
169 | + for (i = 0; i < op->len; i++) | |
170 | + if (op->datbuf[i] != 0xff) | |
171 | + return false; | |
172 | + | |
173 | + for (i = 0; i < op->ooblen; i++) | |
174 | + if (op->oobbuf[i] != 0xff) | |
175 | + return false; | |
176 | + | |
177 | + return true; | |
178 | +} | |
179 | + | |
180 | +static int do_mtd_list(void) | |
181 | +{ | |
182 | + struct mtd_info *mtd; | |
183 | + int dev_nb = 0; | |
184 | + | |
185 | + /* Ensure all devices (and their partitions) are probed */ | |
186 | + mtd_probe_devices(); | |
187 | + | |
188 | + printf("List of MTD devices:\n"); | |
189 | + mtd_for_each_device(mtd) { | |
190 | + if (!mtd_is_partition(mtd)) | |
191 | + mtd_show_device(mtd); | |
192 | + | |
193 | + dev_nb++; | |
194 | + } | |
195 | + | |
196 | + if (!dev_nb) { | |
197 | + printf("No MTD device found\n"); | |
198 | + return CMD_RET_FAILURE; | |
199 | + } | |
200 | + | |
201 | + return CMD_RET_SUCCESS; | |
202 | +} | |
203 | + | |
204 | +static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, | |
205 | + struct mtd_oob_ops *io_op, | |
206 | + bool write_empty_pages, bool woob) | |
207 | +{ | |
208 | + int ret = 0; | |
209 | + | |
210 | + /* | |
211 | + * By default, do not write an empty page. | |
212 | + * Skip it by simulating a successful write. | |
213 | + */ | |
214 | + if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { | |
215 | + io_op->retlen = mtd->writesize; | |
216 | + io_op->oobretlen = woob ? mtd->oobsize : 0; | |
217 | + } else { | |
218 | + ret = mtd_write_oob(mtd, off, io_op); | |
219 | + } | |
220 | + | |
221 | + return ret; | |
222 | +} | |
223 | + | |
224 | +static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | |
225 | +{ | |
226 | + struct mtd_info *mtd; | |
227 | + const char *cmd; | |
228 | + char *mtd_name; | |
229 | + | |
230 | + /* All MTD commands need at least two arguments */ | |
231 | + if (argc < 2) | |
232 | + return CMD_RET_USAGE; | |
233 | + | |
234 | + /* Parse the command name and its optional suffixes */ | |
235 | + cmd = argv[1]; | |
236 | + | |
237 | + /* List the MTD devices if that is what the user wants */ | |
238 | + if (strcmp(cmd, "list") == 0) | |
239 | + return do_mtd_list(); | |
240 | + | |
241 | + /* | |
242 | + * The remaining commands require also at least a device ID. | |
243 | + * Check the selected device is valid. Ensure it is probed. | |
244 | + */ | |
245 | + if (argc < 3) | |
246 | + return CMD_RET_USAGE; | |
247 | + | |
248 | + mtd_name = argv[2]; | |
249 | + mtd_probe_devices(); | |
250 | + mtd = get_mtd_device_nm(mtd_name); | |
251 | + if (IS_ERR_OR_NULL(mtd)) { | |
252 | + printf("MTD device %s not found, ret %ld\n", | |
253 | + mtd_name, PTR_ERR(mtd)); | |
254 | + return CMD_RET_FAILURE; | |
255 | + } | |
256 | + put_mtd_device(mtd); | |
257 | + | |
258 | + argc -= 3; | |
259 | + argv += 3; | |
260 | + | |
261 | + /* Do the parsing */ | |
262 | + if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) || | |
263 | + !strncmp(cmd, "write", 5)) { | |
264 | + bool has_pages = mtd->type == MTD_NANDFLASH || | |
265 | + mtd->type == MTD_MLCNANDFLASH; | |
266 | + bool dump, read, raw, woob, write_empty_pages; | |
267 | + struct mtd_oob_ops io_op = {}; | |
268 | + uint user_addr = 0, npages; | |
269 | + u64 start_off, off, len, remaining, default_len; | |
270 | + u32 oob_len; | |
271 | + u8 *buf; | |
272 | + int ret; | |
273 | + | |
274 | + dump = !strncmp(cmd, "dump", 4); | |
275 | + read = dump || !strncmp(cmd, "read", 4); | |
276 | + raw = strstr(cmd, ".raw"); | |
277 | + woob = strstr(cmd, ".oob"); | |
278 | + write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); | |
279 | + | |
280 | + if (!dump) { | |
281 | + if (!argc) | |
282 | + return CMD_RET_USAGE; | |
283 | + | |
284 | + user_addr = simple_strtoul(argv[0], NULL, 16); | |
285 | + argc--; | |
286 | + argv++; | |
287 | + } | |
288 | + | |
289 | + start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; | |
290 | + if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { | |
291 | + printf("Offset not aligned with a page (0x%x)\n", | |
292 | + mtd->writesize); | |
293 | + return CMD_RET_FAILURE; | |
294 | + } | |
295 | + | |
296 | + default_len = dump ? mtd->writesize : mtd->size; | |
297 | + len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : | |
298 | + default_len; | |
299 | + if (!mtd_is_aligned_with_min_io_size(mtd, len)) { | |
300 | + len = round_up(len, mtd->writesize); | |
301 | + printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", | |
302 | + mtd->writesize, len); | |
303 | + } | |
304 | + | |
305 | + remaining = len; | |
306 | + npages = mtd_len_to_pages(mtd, len); | |
307 | + oob_len = woob ? npages * mtd->oobsize : 0; | |
308 | + | |
309 | + if (dump) | |
310 | + buf = kmalloc(len + oob_len, GFP_KERNEL); | |
311 | + else | |
312 | + buf = map_sysmem(user_addr, 0); | |
313 | + | |
314 | + if (!buf) { | |
315 | + printf("Could not map/allocate the user buffer\n"); | |
316 | + return CMD_RET_FAILURE; | |
317 | + } | |
318 | + | |
319 | + if (has_pages) | |
320 | + printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", | |
321 | + read ? "Reading" : "Writing", len, npages, start_off, | |
322 | + raw ? " [raw]" : "", woob ? " [oob]" : "", | |
323 | + !read && write_empty_pages ? " [dontskipff]" : ""); | |
324 | + else | |
325 | + printf("%s %lld byte(s) at offset 0x%08llx\n", | |
326 | + read ? "Reading" : "Writing", len, start_off); | |
327 | + | |
328 | + io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; | |
329 | + io_op.len = has_pages ? mtd->writesize : len; | |
330 | + io_op.ooblen = woob ? mtd->oobsize : 0; | |
331 | + io_op.datbuf = buf; | |
332 | + io_op.oobbuf = woob ? &buf[len] : NULL; | |
333 | + | |
334 | + /* Search for the first good block after the given offset */ | |
335 | + off = start_off; | |
336 | + while (mtd_block_isbad(mtd, off)) | |
337 | + off += mtd->erasesize; | |
338 | + | |
339 | + /* Loop over the pages to do the actual read/write */ | |
340 | + while (remaining) { | |
341 | + /* Skip the block if it is bad */ | |
342 | + if (mtd_is_aligned_with_block_size(mtd, off) && | |
343 | + mtd_block_isbad(mtd, off)) { | |
344 | + off += mtd->erasesize; | |
345 | + continue; | |
346 | + } | |
347 | + | |
348 | + if (read) | |
349 | + ret = mtd_read_oob(mtd, off, &io_op); | |
350 | + else | |
351 | + ret = mtd_special_write_oob(mtd, off, &io_op, | |
352 | + write_empty_pages, | |
353 | + woob); | |
354 | + | |
355 | + if (ret) { | |
356 | + printf("Failure while %s at offset 0x%llx\n", | |
357 | + read ? "reading" : "writing", off); | |
358 | + return CMD_RET_FAILURE; | |
359 | + } | |
360 | + | |
361 | + off += io_op.retlen; | |
362 | + remaining -= io_op.retlen; | |
363 | + io_op.datbuf += io_op.retlen; | |
364 | + io_op.oobbuf += io_op.oobretlen; | |
365 | + } | |
366 | + | |
367 | + if (!ret && dump) | |
368 | + mtd_dump_device_buf(mtd, start_off, buf, len, woob); | |
369 | + | |
370 | + if (dump) | |
371 | + kfree(buf); | |
372 | + else | |
373 | + unmap_sysmem(buf); | |
374 | + | |
375 | + if (ret) { | |
376 | + printf("%s on %s failed with error %d\n", | |
377 | + read ? "Read" : "Write", mtd->name, ret); | |
378 | + return CMD_RET_FAILURE; | |
379 | + } | |
380 | + | |
381 | + } else if (!strcmp(cmd, "erase")) { | |
382 | + bool scrub = strstr(cmd, ".dontskipbad"); | |
383 | + struct erase_info erase_op = {}; | |
384 | + u64 off, len; | |
385 | + int ret; | |
386 | + | |
387 | + off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; | |
388 | + len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size; | |
389 | + | |
390 | + if (!mtd_is_aligned_with_block_size(mtd, off)) { | |
391 | + printf("Offset not aligned with a block (0x%x)\n", | |
392 | + mtd->erasesize); | |
393 | + return CMD_RET_FAILURE; | |
394 | + } | |
395 | + | |
396 | + if (!mtd_is_aligned_with_block_size(mtd, len)) { | |
397 | + printf("Size not a multiple of a block (0x%x)\n", | |
398 | + mtd->erasesize); | |
399 | + return CMD_RET_FAILURE; | |
400 | + } | |
401 | + | |
402 | + printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", | |
403 | + off, off + len - 1, mtd_div_by_eb(len, mtd)); | |
404 | + | |
405 | + erase_op.mtd = mtd; | |
406 | + erase_op.addr = off; | |
407 | + erase_op.len = len; | |
408 | + erase_op.scrub = scrub; | |
409 | + | |
410 | + while (erase_op.len) { | |
411 | + ret = mtd_erase(mtd, &erase_op); | |
412 | + | |
413 | + /* Abort if its not a bad block error */ | |
414 | + if (ret != -EIO) | |
415 | + break; | |
416 | + | |
417 | + printf("Skipping bad block at 0x%08llx\n", | |
418 | + erase_op.fail_addr); | |
419 | + | |
420 | + /* Skip bad block and continue behind it */ | |
421 | + erase_op.len -= erase_op.fail_addr - erase_op.addr; | |
422 | + erase_op.len -= mtd->erasesize; | |
423 | + erase_op.addr = erase_op.fail_addr + mtd->erasesize; | |
424 | + } | |
425 | + | |
426 | + if (ret && ret != -EIO) | |
427 | + return CMD_RET_FAILURE; | |
428 | + } else if (!strcmp(cmd, "bad")) { | |
429 | + loff_t off; | |
430 | + | |
431 | + if (!mtd_can_have_bb(mtd)) { | |
432 | + printf("Only NAND-based devices can have bad blocks\n"); | |
433 | + return CMD_RET_SUCCESS; | |
434 | + } | |
435 | + | |
436 | + printf("MTD device %s bad blocks list:\n", mtd->name); | |
437 | + for (off = 0; off < mtd->size; off += mtd->erasesize) | |
438 | + if (mtd_block_isbad(mtd, off)) | |
439 | + printf("\t0x%08llx\n", off); | |
440 | + } else { | |
441 | + return CMD_RET_USAGE; | |
442 | + } | |
443 | + | |
444 | + return CMD_RET_SUCCESS; | |
445 | +} | |
446 | + | |
447 | +static char mtd_help_text[] = | |
448 | +#ifdef CONFIG_SYS_LONGHELP | |
449 | + "- generic operations on memory technology devices\n\n" | |
450 | + "mtd list\n" | |
451 | + "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" | |
452 | + "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" | |
453 | + "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" | |
454 | + "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" | |
455 | + "\n" | |
456 | + "Specific functions:\n" | |
457 | + "mtd bad <name>\n" | |
458 | + "\n" | |
459 | + "With:\n" | |
460 | + "\t<name>: NAND partition/chip name\n" | |
461 | + "\t<addr>: user address from/to which data will be retrieved/stored\n" | |
462 | + "\t<off>: offset in <name> in bytes (default: start of the part)\n" | |
463 | + "\t\t* must be block-aligned for erase\n" | |
464 | + "\t\t* must be page-aligned otherwise\n" | |
465 | + "\t<size>: length of the operation in bytes (default: the entire device)\n" | |
466 | + "\t\t* must be a multiple of a block for erase\n" | |
467 | + "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" | |
468 | + "\n" | |
469 | + "The .dontskipff option forces writing empty pages, don't use it if unsure.\n" | |
470 | +#endif | |
471 | + ""; | |
472 | + | |
473 | +U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text); |
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | # (C) Copyright 2000-2007 |
4 | 4 | # Wolfgang Denk, DENX Software Engineering, wd@denx.de. |
5 | 5 | |
6 | -ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF))) | |
6 | +ifneq (,$(findstring y,$(CONFIG_MTD_DEVICE)$(CONFIG_CMD_NAND)$(CONFIG_CMD_ONENAND)$(CONFIG_CMD_SF)$(CONFIG_CMD_MTD))) | |
7 | 7 | obj-y += mtdcore.o mtd_uboot.o |
8 | 8 | endif |
9 | 9 | obj-$(CONFIG_MTD) += mtd-uclass.o |
... | ... | @@ -4,9 +4,16 @@ |
4 | 4 | * Heiko Schocher, DENX Software Engineering, hs@denx.de. |
5 | 5 | */ |
6 | 6 | #include <common.h> |
7 | +#include <dm/device.h> | |
8 | +#include <dm/uclass-internal.h> | |
9 | +#include <jffs2/jffs2.h> /* LEGACY */ | |
7 | 10 | #include <linux/mtd/mtd.h> |
8 | -#include <jffs2/jffs2.h> /* Legacy */ | |
11 | +#include <linux/mtd/partitions.h> | |
12 | +#include <mtd.h> | |
9 | 13 | |
14 | +#define MTD_NAME_MAX_LEN 20 | |
15 | + | |
16 | + | |
10 | 17 | /** |
11 | 18 | * mtd_search_alternate_name - Search an alternate name for @mtdname thanks to |
12 | 19 | * the mtdids legacy environment variable. |
... | ... | @@ -67,6 +74,158 @@ |
67 | 74 | |
68 | 75 | return -EINVAL; |
69 | 76 | } |
77 | + | |
78 | +#if IS_ENABLED(CONFIG_MTD) | |
79 | +static void mtd_probe_uclass_mtd_devs(void) | |
80 | +{ | |
81 | + struct udevice *dev; | |
82 | + int idx = 0; | |
83 | + | |
84 | + /* Probe devices with DM compliant drivers */ | |
85 | + while (!uclass_find_device(UCLASS_MTD, idx, &dev) && dev) { | |
86 | + mtd_probe(dev); | |
87 | + idx++; | |
88 | + } | |
89 | +} | |
90 | +#else | |
91 | +static void mtd_probe_uclass_mtd_devs(void) { } | |
92 | +#endif | |
93 | + | |
94 | +#if defined(CONFIG_MTD_PARTITIONS) | |
95 | +int mtd_probe_devices(void) | |
96 | +{ | |
97 | + static char *old_mtdparts; | |
98 | + static char *old_mtdids; | |
99 | + const char *mtdparts = env_get("mtdparts"); | |
100 | + const char *mtdids = env_get("mtdids"); | |
101 | + bool remaining_partitions = true; | |
102 | + struct mtd_info *mtd; | |
103 | + | |
104 | + mtd_probe_uclass_mtd_devs(); | |
105 | + | |
106 | + /* Check if mtdparts/mtdids changed since last call, otherwise: exit */ | |
107 | + if (!strcmp(mtdparts, old_mtdparts) && !strcmp(mtdids, old_mtdids)) | |
108 | + return 0; | |
109 | + | |
110 | + /* Update the local copy of mtdparts */ | |
111 | + free(old_mtdparts); | |
112 | + free(old_mtdids); | |
113 | + old_mtdparts = strdup(mtdparts); | |
114 | + old_mtdids = strdup(mtdids); | |
115 | + | |
116 | + /* If at least one partition is still in use, do not delete anything */ | |
117 | + mtd_for_each_device(mtd) { | |
118 | + if (mtd->usecount) { | |
119 | + printf("Partition \"%s\" already in use, aborting\n", | |
120 | + mtd->name); | |
121 | + return -EACCES; | |
122 | + } | |
123 | + } | |
124 | + | |
125 | + /* | |
126 | + * Everything looks clear, remove all partitions. It is not safe to | |
127 | + * remove entries from the mtd_for_each_device loop as it uses idr | |
128 | + * indexes and the partitions removal is done in bulk (all partitions of | |
129 | + * one device at the same time), so break and iterate from start each | |
130 | + * time a new partition is found and deleted. | |
131 | + */ | |
132 | + while (remaining_partitions) { | |
133 | + remaining_partitions = false; | |
134 | + mtd_for_each_device(mtd) { | |
135 | + if (!mtd_is_partition(mtd) && mtd_has_partitions(mtd)) { | |
136 | + del_mtd_partitions(mtd); | |
137 | + remaining_partitions = true; | |
138 | + break; | |
139 | + } | |
140 | + } | |
141 | + } | |
142 | + | |
143 | + /* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */ | |
144 | + if (strstr(mtdparts, "mtdparts=")) | |
145 | + mtdparts += 9; | |
146 | + | |
147 | + /* For each MTD device in mtdparts */ | |
148 | + while (mtdparts[0] != '\0') { | |
149 | + char mtd_name[MTD_NAME_MAX_LEN], *colon; | |
150 | + struct mtd_partition *parts; | |
151 | + int mtd_name_len, nparts; | |
152 | + int ret; | |
153 | + | |
154 | + colon = strchr(mtdparts, ':'); | |
155 | + if (!colon) { | |
156 | + printf("Wrong mtdparts: %s\n", mtdparts); | |
157 | + return -EINVAL; | |
158 | + } | |
159 | + | |
160 | + mtd_name_len = colon - mtdparts; | |
161 | + strncpy(mtd_name, mtdparts, mtd_name_len); | |
162 | + mtd_name[mtd_name_len] = '\0'; | |
163 | + /* Move the pointer forward (including the ':') */ | |
164 | + mtdparts += mtd_name_len + 1; | |
165 | + mtd = get_mtd_device_nm(mtd_name); | |
166 | + if (IS_ERR_OR_NULL(mtd)) { | |
167 | + char linux_name[MTD_NAME_MAX_LEN]; | |
168 | + | |
169 | + /* | |
170 | + * The MTD device named "mtd_name" does not exist. Try | |
171 | + * to find a correspondance with an MTD device having | |
172 | + * the same type and number as defined in the mtdids. | |
173 | + */ | |
174 | + debug("No device named %s\n", mtd_name); | |
175 | + ret = mtd_search_alternate_name(mtd_name, linux_name, | |
176 | + MTD_NAME_MAX_LEN); | |
177 | + if (!ret) | |
178 | + mtd = get_mtd_device_nm(linux_name); | |
179 | + | |
180 | + /* | |
181 | + * If no device could be found, move the mtdparts | |
182 | + * pointer forward until the next set of partitions. | |
183 | + */ | |
184 | + if (ret || IS_ERR_OR_NULL(mtd)) { | |
185 | + printf("Could not find a valid device for %s\n", | |
186 | + mtd_name); | |
187 | + mtdparts = strchr(mtdparts, ';'); | |
188 | + if (mtdparts) | |
189 | + mtdparts++; | |
190 | + | |
191 | + continue; | |
192 | + } | |
193 | + } | |
194 | + | |
195 | + /* | |
196 | + * Parse the MTD device partitions. It will update the mtdparts | |
197 | + * pointer, create an array of parts (that must be freed), and | |
198 | + * return the number of partition structures in the array. | |
199 | + */ | |
200 | + ret = mtd_parse_partitions(mtd, &mtdparts, &parts, &nparts); | |
201 | + if (ret) { | |
202 | + printf("Could not parse device %s\n", mtd->name); | |
203 | + put_mtd_device(mtd); | |
204 | + return -EINVAL; | |
205 | + } | |
206 | + | |
207 | + if (!nparts) | |
208 | + continue; | |
209 | + | |
210 | + /* Create the new MTD partitions */ | |
211 | + add_mtd_partitions(mtd, parts, nparts); | |
212 | + | |
213 | + /* Free the structures allocated during the parsing */ | |
214 | + mtd_free_parsed_partitions(parts, nparts); | |
215 | + | |
216 | + put_mtd_device(mtd); | |
217 | + } | |
218 | + | |
219 | + return 0; | |
220 | +} | |
221 | +#else | |
222 | +int mtd_probe_devices(void) | |
223 | +{ | |
224 | + mtd_probe_uclass_mtd_devs(); | |
225 | + | |
226 | + return 0; | |
227 | +} | |
228 | +#endif /* defined(CONFIG_MTD_PARTITIONS) */ | |
70 | 229 | |
71 | 230 | /* Legacy */ |
72 | 231 |
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af
-
mentioned in commit c86020
-
mentioned in commit 5ffcd5
-
mentioned in commit 779c9c
-
mentioned in commit 429e04
-
mentioned in commit 772aa9
-
mentioned in commit 2428d9
-
mentioned in commit 4a5594
-
mentioned in commit 6015af