]>
Commit | Line | Data |
---|---|---|
5db66b3a MR |
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 <[email protected]> | |
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); |