]>
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> | |
5db66b3a | 12 | #include <console.h> |
0319bae9 | 13 | #include <led.h> |
248fc160 AK |
14 | #if CONFIG_IS_ENABLED(CMD_MTD_OTP) |
15 | #include <hexdump.h> | |
16 | #endif | |
5db66b3a MR |
17 | #include <malloc.h> |
18 | #include <mapmem.h> | |
19 | #include <mtd.h> | |
61b29b82 SG |
20 | #include <dm/devres.h> |
21 | #include <linux/err.h> | |
5db66b3a | 22 | |
9671243e BB |
23 | #include <linux/ctype.h> |
24 | ||
25 | static struct mtd_info *get_mtd_by_name(const char *name) | |
26 | { | |
27 | struct mtd_info *mtd; | |
28 | ||
29 | mtd_probe_devices(); | |
30 | ||
31 | mtd = get_mtd_device_nm(name); | |
32 | if (IS_ERR_OR_NULL(mtd)) | |
33 | printf("MTD device %s not found, ret %ld\n", name, | |
34 | PTR_ERR(mtd)); | |
35 | ||
36 | return mtd; | |
37 | } | |
38 | ||
5db66b3a MR |
39 | static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) |
40 | { | |
41 | do_div(len, mtd->writesize); | |
42 | ||
43 | return len; | |
44 | } | |
45 | ||
46 | static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) | |
47 | { | |
48 | return !do_div(size, mtd->writesize); | |
49 | } | |
50 | ||
51 | static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) | |
52 | { | |
53 | return !do_div(size, mtd->erasesize); | |
54 | } | |
55 | ||
56 | static void mtd_dump_buf(const u8 *buf, uint len, uint offset) | |
57 | { | |
58 | int i, j; | |
59 | ||
60 | for (i = 0; i < len; ) { | |
61 | printf("0x%08x:\t", offset + i); | |
62 | for (j = 0; j < 8; j++) | |
63 | printf("%02x ", buf[i + j]); | |
64 | printf(" "); | |
65 | i += 8; | |
66 | for (j = 0; j < 8; j++) | |
67 | printf("%02x ", buf[i + j]); | |
68 | printf("\n"); | |
69 | i += 8; | |
70 | } | |
71 | } | |
72 | ||
73 | static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, | |
74 | const u8 *buf, u64 len, bool woob) | |
75 | { | |
76 | bool has_pages = mtd->type == MTD_NANDFLASH || | |
77 | mtd->type == MTD_MLCNANDFLASH; | |
78 | int npages = mtd_len_to_pages(mtd, len); | |
79 | uint page; | |
80 | ||
81 | if (has_pages) { | |
82 | for (page = 0; page < npages; page++) { | |
6b37320c | 83 | u64 data_off = (u64)page * mtd->writesize; |
5db66b3a MR |
84 | |
85 | printf("\nDump %d data bytes from 0x%08llx:\n", | |
86 | mtd->writesize, start_off + data_off); | |
87 | mtd_dump_buf(&buf[data_off], | |
88 | mtd->writesize, start_off + data_off); | |
89 | ||
90 | if (woob) { | |
6b37320c | 91 | u64 oob_off = (u64)page * mtd->oobsize; |
5db66b3a MR |
92 | |
93 | printf("Dump %d OOB bytes from page at 0x%08llx:\n", | |
94 | mtd->oobsize, start_off + data_off); | |
95 | mtd_dump_buf(&buf[len + oob_off], | |
96 | mtd->oobsize, 0); | |
97 | } | |
98 | } | |
99 | } else { | |
100 | printf("\nDump %lld data bytes from 0x%llx:\n", | |
101 | len, start_off); | |
102 | mtd_dump_buf(buf, len, start_off); | |
103 | } | |
104 | } | |
105 | ||
106 | static void mtd_show_parts(struct mtd_info *mtd, int level) | |
107 | { | |
108 | struct mtd_info *part; | |
109 | int i; | |
110 | ||
111 | list_for_each_entry(part, &mtd->partitions, node) { | |
112 | for (i = 0; i < level; i++) | |
113 | printf("\t"); | |
114 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
115 | part->offset, part->offset + part->size, part->name); | |
116 | ||
117 | mtd_show_parts(part, level + 1); | |
118 | } | |
119 | } | |
120 | ||
121 | static void mtd_show_device(struct mtd_info *mtd) | |
122 | { | |
123 | /* Device */ | |
124 | printf("* %s\n", mtd->name); | |
125 | #if defined(CONFIG_DM) | |
126 | if (mtd->dev) { | |
127 | printf(" - device: %s\n", mtd->dev->name); | |
128 | printf(" - parent: %s\n", mtd->dev->parent->name); | |
129 | printf(" - driver: %s\n", mtd->dev->driver->name); | |
130 | } | |
131 | #endif | |
0b6f907d MB |
132 | if (IS_ENABLED(CONFIG_OF_CONTROL) && mtd->dev) { |
133 | char buf[256]; | |
134 | int res; | |
135 | ||
136 | res = ofnode_get_path(mtd_get_ofnode(mtd), buf, 256); | |
137 | printf(" - path: %s\n", res == 0 ? buf : "unavailable"); | |
138 | } | |
5db66b3a MR |
139 | |
140 | /* MTD device information */ | |
141 | printf(" - type: "); | |
142 | switch (mtd->type) { | |
143 | case MTD_RAM: | |
144 | printf("RAM\n"); | |
145 | break; | |
146 | case MTD_ROM: | |
147 | printf("ROM\n"); | |
148 | break; | |
149 | case MTD_NORFLASH: | |
150 | printf("NOR flash\n"); | |
151 | break; | |
152 | case MTD_NANDFLASH: | |
153 | printf("NAND flash\n"); | |
154 | break; | |
155 | case MTD_DATAFLASH: | |
156 | printf("Data flash\n"); | |
157 | break; | |
158 | case MTD_UBIVOLUME: | |
159 | printf("UBI volume\n"); | |
160 | break; | |
161 | case MTD_MLCNANDFLASH: | |
162 | printf("MLC NAND flash\n"); | |
163 | break; | |
164 | case MTD_ABSENT: | |
165 | default: | |
166 | printf("Unknown\n"); | |
167 | break; | |
168 | } | |
169 | ||
170 | printf(" - block size: 0x%x bytes\n", mtd->erasesize); | |
171 | printf(" - min I/O: 0x%x bytes\n", mtd->writesize); | |
172 | ||
173 | if (mtd->oobsize) { | |
174 | printf(" - OOB size: %u bytes\n", mtd->oobsize); | |
175 | printf(" - OOB available: %u bytes\n", mtd->oobavail); | |
176 | } | |
177 | ||
178 | if (mtd->ecc_strength) { | |
179 | printf(" - ECC strength: %u bits\n", mtd->ecc_strength); | |
180 | printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); | |
181 | printf(" - bitflip threshold: %u bits\n", | |
182 | mtd->bitflip_threshold); | |
183 | } | |
184 | ||
185 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
186 | mtd->offset, mtd->offset + mtd->size, mtd->name); | |
187 | ||
188 | /* MTD partitions, if any */ | |
189 | mtd_show_parts(mtd, 1); | |
190 | } | |
191 | ||
192 | /* Logic taken from fs/ubifs/recovery.c:is_empty() */ | |
193 | static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) | |
194 | { | |
195 | int i; | |
196 | ||
197 | for (i = 0; i < op->len; i++) | |
198 | if (op->datbuf[i] != 0xff) | |
199 | return false; | |
200 | ||
201 | for (i = 0; i < op->ooblen; i++) | |
202 | if (op->oobbuf[i] != 0xff) | |
203 | return false; | |
204 | ||
205 | return true; | |
206 | } | |
207 | ||
248fc160 AK |
208 | #if CONFIG_IS_ENABLED(CMD_MTD_OTP) |
209 | static int do_mtd_otp_read(struct cmd_tbl *cmdtp, int flag, int argc, | |
210 | char *const argv[]) | |
211 | { | |
212 | struct mtd_info *mtd; | |
213 | size_t retlen; | |
214 | off_t from; | |
215 | size_t len; | |
216 | bool user; | |
217 | int ret; | |
218 | u8 *buf; | |
219 | ||
220 | if (argc != 5) | |
221 | return CMD_RET_USAGE; | |
222 | ||
223 | if (!strcmp(argv[2], "u")) | |
224 | user = true; | |
225 | else if (!strcmp(argv[2], "f")) | |
226 | user = false; | |
227 | else | |
228 | return CMD_RET_USAGE; | |
229 | ||
230 | mtd = get_mtd_by_name(argv[1]); | |
231 | if (IS_ERR_OR_NULL(mtd)) | |
232 | return CMD_RET_FAILURE; | |
233 | ||
234 | from = simple_strtoul(argv[3], NULL, 0); | |
235 | len = simple_strtoul(argv[4], NULL, 0); | |
236 | ||
237 | ret = CMD_RET_FAILURE; | |
238 | ||
239 | buf = malloc(len); | |
240 | if (!buf) | |
241 | goto put_mtd; | |
242 | ||
243 | printf("Reading %s OTP from 0x%lx, %zu bytes\n", | |
244 | user ? "user" : "factory", from, len); | |
245 | ||
246 | if (user) | |
247 | ret = mtd_read_user_prot_reg(mtd, from, len, &retlen, buf); | |
248 | else | |
249 | ret = mtd_read_fact_prot_reg(mtd, from, len, &retlen, buf); | |
250 | if (ret) { | |
251 | free(buf); | |
252 | pr_err("OTP read failed: %d\n", ret); | |
253 | ret = CMD_RET_FAILURE; | |
254 | goto put_mtd; | |
255 | } | |
256 | ||
257 | if (retlen != len) | |
258 | pr_err("OTP read returns %zu, but %zu expected\n", | |
259 | retlen, len); | |
260 | ||
261 | print_hex_dump("", 0, 16, 1, buf, retlen, true); | |
262 | ||
263 | free(buf); | |
264 | ||
265 | ret = CMD_RET_SUCCESS; | |
266 | ||
267 | put_mtd: | |
268 | put_mtd_device(mtd); | |
269 | ||
270 | return ret; | |
271 | } | |
272 | ||
273 | static int do_mtd_otp_lock(struct cmd_tbl *cmdtp, int flag, int argc, | |
274 | char *const argv[]) | |
275 | { | |
276 | struct mtd_info *mtd; | |
277 | off_t from; | |
278 | size_t len; | |
279 | int ret; | |
280 | ||
281 | if (argc != 4) | |
282 | return CMD_RET_USAGE; | |
283 | ||
284 | mtd = get_mtd_by_name(argv[1]); | |
285 | if (IS_ERR_OR_NULL(mtd)) | |
286 | return CMD_RET_FAILURE; | |
287 | ||
288 | from = simple_strtoul(argv[2], NULL, 0); | |
289 | len = simple_strtoul(argv[3], NULL, 0); | |
290 | ||
291 | ret = mtd_lock_user_prot_reg(mtd, from, len); | |
292 | if (ret) { | |
293 | pr_err("OTP lock failed: %d\n", ret); | |
294 | ret = CMD_RET_FAILURE; | |
295 | goto put_mtd; | |
296 | } | |
297 | ||
298 | ret = CMD_RET_SUCCESS; | |
299 | ||
300 | put_mtd: | |
301 | put_mtd_device(mtd); | |
302 | ||
303 | return ret; | |
304 | } | |
305 | ||
306 | static int do_mtd_otp_write(struct cmd_tbl *cmdtp, int flag, int argc, | |
307 | char *const argv[]) | |
308 | { | |
309 | struct mtd_info *mtd; | |
310 | size_t retlen; | |
311 | size_t binlen; | |
312 | u8 *binbuf; | |
313 | off_t from; | |
314 | int ret; | |
315 | ||
316 | if (argc != 4) | |
317 | return CMD_RET_USAGE; | |
318 | ||
319 | mtd = get_mtd_by_name(argv[1]); | |
320 | if (IS_ERR_OR_NULL(mtd)) | |
321 | return CMD_RET_FAILURE; | |
322 | ||
323 | from = simple_strtoul(argv[2], NULL, 0); | |
324 | binlen = strlen(argv[3]) / 2; | |
325 | ||
326 | ret = CMD_RET_FAILURE; | |
327 | binbuf = malloc(binlen); | |
328 | if (!binbuf) | |
329 | goto put_mtd; | |
330 | ||
331 | hex2bin(binbuf, argv[3], binlen); | |
332 | ||
333 | printf("Will write:\n"); | |
334 | ||
335 | print_hex_dump("", 0, 16, 1, binbuf, binlen, true); | |
336 | ||
337 | printf("to 0x%lx\n", from); | |
338 | ||
339 | printf("Continue (y/n)?\n"); | |
340 | ||
341 | if (confirm_yesno() != 1) { | |
342 | pr_err("OTP write canceled\n"); | |
343 | ret = CMD_RET_SUCCESS; | |
344 | goto put_mtd; | |
345 | } | |
346 | ||
347 | ret = mtd_write_user_prot_reg(mtd, from, binlen, &retlen, binbuf); | |
348 | if (ret) { | |
349 | pr_err("OTP write failed: %d\n", ret); | |
350 | ret = CMD_RET_FAILURE; | |
351 | goto put_mtd; | |
352 | } | |
353 | ||
354 | if (retlen != binlen) | |
355 | pr_err("OTP write returns %zu, but %zu expected\n", | |
356 | retlen, binlen); | |
357 | ||
358 | ret = CMD_RET_SUCCESS; | |
359 | ||
360 | put_mtd: | |
361 | free(binbuf); | |
362 | put_mtd_device(mtd); | |
363 | ||
364 | return ret; | |
365 | } | |
366 | ||
367 | static int do_mtd_otp_info(struct cmd_tbl *cmdtp, int flag, int argc, | |
368 | char *const argv[]) | |
369 | { | |
370 | struct otp_info otp_info; | |
371 | struct mtd_info *mtd; | |
372 | size_t retlen; | |
373 | bool user; | |
374 | int ret; | |
375 | ||
376 | if (argc != 3) | |
377 | return CMD_RET_USAGE; | |
378 | ||
379 | if (!strcmp(argv[2], "u")) | |
380 | user = true; | |
381 | else if (!strcmp(argv[2], "f")) | |
382 | user = false; | |
383 | else | |
384 | return CMD_RET_USAGE; | |
385 | ||
386 | mtd = get_mtd_by_name(argv[1]); | |
387 | if (IS_ERR_OR_NULL(mtd)) | |
388 | return CMD_RET_FAILURE; | |
389 | ||
390 | if (user) | |
391 | ret = mtd_get_user_prot_info(mtd, sizeof(otp_info), &retlen, | |
392 | &otp_info); | |
393 | else | |
394 | ret = mtd_get_fact_prot_info(mtd, sizeof(otp_info), &retlen, | |
395 | &otp_info); | |
396 | if (ret) { | |
397 | pr_err("OTP info failed: %d\n", ret); | |
398 | ret = CMD_RET_FAILURE; | |
399 | goto put_mtd; | |
400 | } | |
401 | ||
402 | if (retlen != sizeof(otp_info)) { | |
403 | pr_err("OTP info returns %zu, but %zu expected\n", | |
404 | retlen, sizeof(otp_info)); | |
405 | ret = CMD_RET_FAILURE; | |
406 | goto put_mtd; | |
407 | } | |
408 | ||
409 | printf("%s OTP region info:\n", user ? "User" : "Factory"); | |
410 | printf("\tstart: %u\n", otp_info.start); | |
411 | printf("\tlength: %u\n", otp_info.length); | |
412 | printf("\tlocked: %u\n", otp_info.locked); | |
413 | ||
414 | ret = CMD_RET_SUCCESS; | |
415 | ||
416 | put_mtd: | |
417 | put_mtd_device(mtd); | |
418 | ||
419 | return ret; | |
420 | } | |
421 | #endif | |
422 | ||
09140113 SG |
423 | static int do_mtd_list(struct cmd_tbl *cmdtp, int flag, int argc, |
424 | char *const argv[]) | |
5db66b3a MR |
425 | { |
426 | struct mtd_info *mtd; | |
427 | int dev_nb = 0; | |
428 | ||
429 | /* Ensure all devices (and their partitions) are probed */ | |
430 | mtd_probe_devices(); | |
431 | ||
432 | printf("List of MTD devices:\n"); | |
433 | mtd_for_each_device(mtd) { | |
434 | if (!mtd_is_partition(mtd)) | |
435 | mtd_show_device(mtd); | |
436 | ||
437 | dev_nb++; | |
438 | } | |
439 | ||
440 | if (!dev_nb) { | |
441 | printf("No MTD device found\n"); | |
442 | return CMD_RET_FAILURE; | |
443 | } | |
444 | ||
445 | return CMD_RET_SUCCESS; | |
446 | } | |
447 | ||
448 | static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, | |
449 | struct mtd_oob_ops *io_op, | |
450 | bool write_empty_pages, bool woob) | |
451 | { | |
452 | int ret = 0; | |
453 | ||
454 | /* | |
455 | * By default, do not write an empty page. | |
456 | * Skip it by simulating a successful write. | |
457 | */ | |
458 | if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { | |
459 | io_op->retlen = mtd->writesize; | |
460 | io_op->oobretlen = woob ? mtd->oobsize : 0; | |
461 | } else { | |
462 | ret = mtd_write_oob(mtd, off, io_op); | |
463 | } | |
464 | ||
465 | return ret; | |
466 | } | |
467 | ||
09140113 SG |
468 | static int do_mtd_io(struct cmd_tbl *cmdtp, int flag, int argc, |
469 | char *const argv[]) | |
5db66b3a | 470 | { |
9671243e BB |
471 | bool dump, read, raw, woob, write_empty_pages, has_pages = false; |
472 | u64 start_off, off, len, remaining, default_len; | |
473 | struct mtd_oob_ops io_op = {}; | |
474 | uint user_addr = 0, npages; | |
475 | const char *cmd = argv[0]; | |
5db66b3a | 476 | struct mtd_info *mtd; |
9671243e BB |
477 | u32 oob_len; |
478 | u8 *buf; | |
479 | int ret; | |
5db66b3a | 480 | |
5db66b3a MR |
481 | if (argc < 2) |
482 | return CMD_RET_USAGE; | |
483 | ||
9671243e BB |
484 | mtd = get_mtd_by_name(argv[1]); |
485 | if (IS_ERR_OR_NULL(mtd)) | |
486 | return CMD_RET_FAILURE; | |
5db66b3a | 487 | |
9671243e BB |
488 | if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH) |
489 | has_pages = true; | |
5db66b3a | 490 | |
9671243e BB |
491 | dump = !strncmp(cmd, "dump", 4); |
492 | read = dump || !strncmp(cmd, "read", 4); | |
493 | raw = strstr(cmd, ".raw"); | |
494 | woob = strstr(cmd, ".oob"); | |
495 | write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); | |
5db66b3a | 496 | |
9671243e BB |
497 | argc -= 2; |
498 | argv += 2; | |
5db66b3a | 499 | |
9671243e BB |
500 | if (!dump) { |
501 | if (!argc) { | |
502 | ret = CMD_RET_USAGE; | |
503 | goto out_put_mtd; | |
5db66b3a MR |
504 | } |
505 | ||
7e5f460e | 506 | user_addr = hextoul(argv[0], NULL); |
9671243e BB |
507 | argc--; |
508 | argv++; | |
509 | } | |
5db66b3a | 510 | |
7e5f460e | 511 | start_off = argc > 0 ? hextoul(argv[0], NULL) : 0; |
9671243e BB |
512 | if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { |
513 | printf("Offset not aligned with a page (0x%x)\n", | |
514 | mtd->writesize); | |
515 | ret = CMD_RET_FAILURE; | |
516 | goto out_put_mtd; | |
517 | } | |
5db66b3a | 518 | |
9671243e | 519 | default_len = dump ? mtd->writesize : mtd->size; |
7e5f460e | 520 | len = argc > 1 ? hextoul(argv[1], NULL) : default_len; |
9671243e BB |
521 | if (!mtd_is_aligned_with_min_io_size(mtd, len)) { |
522 | len = round_up(len, mtd->writesize); | |
523 | printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", | |
524 | mtd->writesize, len); | |
525 | } | |
5db66b3a | 526 | |
9671243e BB |
527 | remaining = len; |
528 | npages = mtd_len_to_pages(mtd, len); | |
529 | oob_len = woob ? npages * mtd->oobsize : 0; | |
5db66b3a | 530 | |
9671243e BB |
531 | if (dump) |
532 | buf = kmalloc(len + oob_len, GFP_KERNEL); | |
533 | else | |
534 | buf = map_sysmem(user_addr, 0); | |
535 | ||
536 | if (!buf) { | |
537 | printf("Could not map/allocate the user buffer\n"); | |
538 | ret = CMD_RET_FAILURE; | |
539 | goto out_put_mtd; | |
540 | } | |
541 | ||
542 | if (has_pages) | |
543 | printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", | |
544 | read ? "Reading" : "Writing", len, npages, start_off, | |
545 | raw ? " [raw]" : "", woob ? " [oob]" : "", | |
546 | !read && write_empty_pages ? " [dontskipff]" : ""); | |
547 | else | |
548 | printf("%s %lld byte(s) at offset 0x%08llx\n", | |
549 | read ? "Reading" : "Writing", len, start_off); | |
550 | ||
551 | io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; | |
552 | io_op.len = has_pages ? mtd->writesize : len; | |
553 | io_op.ooblen = woob ? mtd->oobsize : 0; | |
554 | io_op.datbuf = buf; | |
555 | io_op.oobbuf = woob ? &buf[len] : NULL; | |
556 | ||
557 | /* Search for the first good block after the given offset */ | |
558 | off = start_off; | |
559 | while (mtd_block_isbad(mtd, off)) | |
560 | off += mtd->erasesize; | |
561 | ||
0319bae9 CM |
562 | led_activity_blink(); |
563 | ||
9671243e BB |
564 | /* Loop over the pages to do the actual read/write */ |
565 | while (remaining) { | |
566 | /* Skip the block if it is bad */ | |
567 | if (mtd_is_aligned_with_block_size(mtd, off) && | |
568 | mtd_block_isbad(mtd, off)) { | |
569 | off += mtd->erasesize; | |
570 | continue; | |
5db66b3a MR |
571 | } |
572 | ||
9671243e BB |
573 | if (read) |
574 | ret = mtd_read_oob(mtd, off, &io_op); | |
5db66b3a | 575 | else |
9671243e BB |
576 | ret = mtd_special_write_oob(mtd, off, &io_op, |
577 | write_empty_pages, woob); | |
5db66b3a | 578 | |
9671243e BB |
579 | if (ret) { |
580 | printf("Failure while %s at offset 0x%llx\n", | |
581 | read ? "reading" : "writing", off); | |
582 | break; | |
583 | } | |
5db66b3a | 584 | |
9671243e BB |
585 | off += io_op.retlen; |
586 | remaining -= io_op.retlen; | |
587 | io_op.datbuf += io_op.retlen; | |
588 | io_op.oobbuf += io_op.oobretlen; | |
589 | } | |
5db66b3a | 590 | |
0319bae9 CM |
591 | led_activity_off(); |
592 | ||
9671243e BB |
593 | if (!ret && dump) |
594 | mtd_dump_device_buf(mtd, start_off, buf, len, woob); | |
5db66b3a | 595 | |
9671243e BB |
596 | if (dump) |
597 | kfree(buf); | |
598 | else | |
599 | unmap_sysmem(buf); | |
5db66b3a | 600 | |
9671243e BB |
601 | if (ret) { |
602 | printf("%s on %s failed with error %d\n", | |
603 | read ? "Read" : "Write", mtd->name, ret); | |
604 | ret = CMD_RET_FAILURE; | |
605 | } else { | |
606 | ret = CMD_RET_SUCCESS; | |
607 | } | |
5db66b3a | 608 | |
9671243e BB |
609 | out_put_mtd: |
610 | put_mtd_device(mtd); | |
5db66b3a | 611 | |
9671243e BB |
612 | return ret; |
613 | } | |
5db66b3a | 614 | |
09140113 SG |
615 | static int do_mtd_erase(struct cmd_tbl *cmdtp, int flag, int argc, |
616 | char *const argv[]) | |
9671243e BB |
617 | { |
618 | struct erase_info erase_op = {}; | |
619 | struct mtd_info *mtd; | |
620 | u64 off, len; | |
621 | bool scrub; | |
b1b147f2 | 622 | int ret = 0; |
5db66b3a | 623 | |
9671243e BB |
624 | if (argc < 2) |
625 | return CMD_RET_USAGE; | |
5db66b3a | 626 | |
9671243e BB |
627 | mtd = get_mtd_by_name(argv[1]); |
628 | if (IS_ERR_OR_NULL(mtd)) | |
629 | return CMD_RET_FAILURE; | |
5db66b3a | 630 | |
9671243e | 631 | scrub = strstr(argv[0], ".dontskipbad"); |
5db66b3a | 632 | |
9671243e BB |
633 | argc -= 2; |
634 | argv += 2; | |
5db66b3a | 635 | |
7e5f460e SG |
636 | off = argc > 0 ? hextoul(argv[0], NULL) : 0; |
637 | len = argc > 1 ? hextoul(argv[1], NULL) : mtd->size; | |
5db66b3a | 638 | |
9671243e BB |
639 | if (!mtd_is_aligned_with_block_size(mtd, off)) { |
640 | printf("Offset not aligned with a block (0x%x)\n", | |
641 | mtd->erasesize); | |
642 | ret = CMD_RET_FAILURE; | |
643 | goto out_put_mtd; | |
644 | } | |
5db66b3a | 645 | |
9671243e BB |
646 | if (!mtd_is_aligned_with_block_size(mtd, len)) { |
647 | printf("Size not a multiple of a block (0x%x)\n", | |
648 | mtd->erasesize); | |
649 | ret = CMD_RET_FAILURE; | |
650 | goto out_put_mtd; | |
651 | } | |
5db66b3a | 652 | |
9671243e BB |
653 | printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", |
654 | off, off + len - 1, mtd_div_by_eb(len, mtd)); | |
5db66b3a | 655 | |
9671243e BB |
656 | erase_op.mtd = mtd; |
657 | erase_op.addr = off; | |
b1b147f2 | 658 | erase_op.len = mtd->erasesize; |
5db66b3a | 659 | |
0319bae9 CM |
660 | led_activity_blink(); |
661 | ||
b1b147f2 | 662 | while (len) { |
d09807ad DB |
663 | if (!scrub) { |
664 | ret = mtd_block_isbad(mtd, erase_op.addr); | |
665 | if (ret < 0) { | |
666 | printf("Failed to get bad block at 0x%08llx\n", | |
667 | erase_op.addr); | |
668 | ret = CMD_RET_FAILURE; | |
669 | goto out_put_mtd; | |
670 | } | |
5db66b3a | 671 | |
d09807ad DB |
672 | if (ret > 0) { |
673 | printf("Skipping bad block at 0x%08llx\n", | |
674 | erase_op.addr); | |
675 | ret = 0; | |
676 | len -= mtd->erasesize; | |
677 | erase_op.addr += mtd->erasesize; | |
678 | continue; | |
679 | } | |
b1b147f2 | 680 | } |
9671243e | 681 | |
d09807ad DB |
682 | ret = mtd_erase(mtd, &erase_op); |
683 | if (ret && ret != -EIO) | |
684 | break; | |
685 | ||
b1b147f2 PD |
686 | len -= mtd->erasesize; |
687 | erase_op.addr += mtd->erasesize; | |
9671243e BB |
688 | } |
689 | ||
0319bae9 CM |
690 | led_activity_off(); |
691 | ||
9671243e BB |
692 | if (ret && ret != -EIO) |
693 | ret = CMD_RET_FAILURE; | |
694 | else | |
695 | ret = CMD_RET_SUCCESS; | |
696 | ||
697 | out_put_mtd: | |
698 | put_mtd_device(mtd); | |
699 | ||
700 | return ret; | |
701 | } | |
702 | ||
09140113 SG |
703 | static int do_mtd_bad(struct cmd_tbl *cmdtp, int flag, int argc, |
704 | char *const argv[]) | |
9671243e BB |
705 | { |
706 | struct mtd_info *mtd; | |
707 | loff_t off; | |
708 | ||
709 | if (argc < 2) | |
5db66b3a | 710 | return CMD_RET_USAGE; |
9671243e BB |
711 | |
712 | mtd = get_mtd_by_name(argv[1]); | |
713 | if (IS_ERR_OR_NULL(mtd)) | |
714 | return CMD_RET_FAILURE; | |
715 | ||
716 | if (!mtd_can_have_bb(mtd)) { | |
717 | printf("Only NAND-based devices can have bad blocks\n"); | |
718 | goto out_put_mtd; | |
719 | } | |
720 | ||
721 | printf("MTD device %s bad blocks list:\n", mtd->name); | |
722 | for (off = 0; off < mtd->size; off += mtd->erasesize) { | |
723 | if (mtd_block_isbad(mtd, off)) | |
724 | printf("\t0x%08llx\n", off); | |
5db66b3a MR |
725 | } |
726 | ||
9671243e BB |
727 | out_put_mtd: |
728 | put_mtd_device(mtd); | |
729 | ||
5db66b3a MR |
730 | return CMD_RET_SUCCESS; |
731 | } | |
732 | ||
9671243e | 733 | #ifdef CONFIG_AUTO_COMPLETE |
09140113 | 734 | static int mtd_name_complete(int argc, char *const argv[], char last_char, |
9671243e BB |
735 | int maxv, char *cmdv[]) |
736 | { | |
737 | int len = 0, n_found = 0; | |
738 | struct mtd_info *mtd; | |
739 | ||
740 | argc--; | |
741 | argv++; | |
742 | ||
743 | if (argc > 1 || | |
744 | (argc == 1 && (last_char == '\0' || isblank(last_char)))) | |
745 | return 0; | |
746 | ||
747 | if (argc) | |
748 | len = strlen(argv[0]); | |
749 | ||
750 | mtd_for_each_device(mtd) { | |
751 | if (argc && | |
752 | (len > strlen(mtd->name) || | |
753 | strncmp(argv[0], mtd->name, len))) | |
754 | continue; | |
755 | ||
756 | if (n_found >= maxv - 2) { | |
757 | cmdv[n_found++] = "..."; | |
758 | break; | |
759 | } | |
760 | ||
761 | cmdv[n_found++] = mtd->name; | |
762 | } | |
763 | ||
764 | cmdv[n_found] = NULL; | |
765 | ||
766 | return n_found; | |
767 | } | |
768 | #endif /* CONFIG_AUTO_COMPLETE */ | |
769 | ||
3616218b | 770 | U_BOOT_LONGHELP(mtd, |
5db66b3a MR |
771 | "- generic operations on memory technology devices\n\n" |
772 | "mtd list\n" | |
773 | "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" | |
774 | "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" | |
775 | "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" | |
776 | "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" | |
777 | "\n" | |
778 | "Specific functions:\n" | |
779 | "mtd bad <name>\n" | |
248fc160 AK |
780 | #if CONFIG_IS_ENABLED(CMD_MTD_OTP) |
781 | "mtd otpread <name> [u|f] <off> <size>\n" | |
782 | "mtd otpwrite <name> <off> <hex string>\n" | |
783 | "mtd otplock <name> <off> <size>\n" | |
784 | "mtd otpinfo <name> [u|f]\n" | |
785 | #endif | |
5db66b3a MR |
786 | "\n" |
787 | "With:\n" | |
e41a2bc6 | 788 | "\t<name>: NAND partition/chip name (or corresponding DM device name or OF path)\n" |
5db66b3a MR |
789 | "\t<addr>: user address from/to which data will be retrieved/stored\n" |
790 | "\t<off>: offset in <name> in bytes (default: start of the part)\n" | |
791 | "\t\t* must be block-aligned for erase\n" | |
792 | "\t\t* must be page-aligned otherwise\n" | |
793 | "\t<size>: length of the operation in bytes (default: the entire device)\n" | |
794 | "\t\t* must be a multiple of a block for erase\n" | |
795 | "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" | |
248fc160 AK |
796 | #if CONFIG_IS_ENABLED(CMD_MTD_OTP) |
797 | "\t<hex string>: hex string without '0x' and spaces. Example: ABCD1234\n" | |
798 | "\t[u|f]: user or factory OTP region\n" | |
799 | #endif | |
5db66b3a | 800 | "\n" |
3616218b | 801 | "The .dontskipff option forces writing empty pages, don't use it if unsure.\n"); |
5db66b3a | 802 | |
9671243e | 803 | U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, |
248fc160 AK |
804 | #if CONFIG_IS_ENABLED(CMD_MTD_OTP) |
805 | U_BOOT_SUBCMD_MKENT(otpread, 5, 1, do_mtd_otp_read), | |
806 | U_BOOT_SUBCMD_MKENT(otpwrite, 4, 1, do_mtd_otp_write), | |
807 | U_BOOT_SUBCMD_MKENT(otplock, 4, 1, do_mtd_otp_lock), | |
808 | U_BOOT_SUBCMD_MKENT(otpinfo, 3, 1, do_mtd_otp_info), | |
809 | #endif | |
9671243e BB |
810 | U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list), |
811 | U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io, | |
812 | mtd_name_complete), | |
813 | U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io, | |
814 | mtd_name_complete), | |
815 | U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io, | |
816 | mtd_name_complete), | |
817 | U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, | |
818 | mtd_name_complete), | |
819 | U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, | |
820 | mtd_name_complete)); |