mtd.c (592cd5defd4f71d34ffcbd8dd3326bc10f662e20) | mtd.c (9671243e8d10defb06f2ea24fac138c87697d7fc) |
---|---|
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 | 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#include <linux/ctype.h> 19 20static struct mtd_info *get_mtd_by_name(const char *name) 21{ 22 struct mtd_info *mtd; 23 24 mtd_probe_devices(); 25 26 mtd = get_mtd_device_nm(name); 27 if (IS_ERR_OR_NULL(mtd)) 28 printf("MTD device %s not found, ret %ld\n", name, 29 PTR_ERR(mtd)); 30 31 return mtd; 32} 33 |
|
18static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) 19{ 20 do_div(len, mtd->writesize); 21 22 return len; 23} 24 25static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) --- 146 unchanged lines hidden (view full) --- 172 173 for (i = 0; i < op->ooblen; i++) 174 if (op->oobbuf[i] != 0xff) 175 return false; 176 177 return true; 178} 179 | 34static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) 35{ 36 do_div(len, mtd->writesize); 37 38 return len; 39} 40 41static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) --- 146 unchanged lines hidden (view full) --- 188 189 for (i = 0; i < op->ooblen; i++) 190 if (op->oobbuf[i] != 0xff) 191 return false; 192 193 return true; 194} 195 |
180static int do_mtd_list(void) | 196static int do_mtd_list(cmd_tbl_t *cmdtp, int flag, int argc, 197 char * const argv[]) |
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"); --- 27 unchanged lines hidden (view full) --- 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 | 198{ 199 struct mtd_info *mtd; 200 int dev_nb = 0; 201 202 /* Ensure all devices (and their partitions) are probed */ 203 mtd_probe_devices(); 204 205 printf("List of MTD devices:\n"); --- 27 unchanged lines hidden (view full) --- 233 io_op->oobretlen = woob ? mtd->oobsize : 0; 234 } else { 235 ret = mtd_write_oob(mtd, off, io_op); 236 } 237 238 return ret; 239} 240 |
224static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) | 241static int do_mtd_io(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
225{ | 242{ |
243 bool dump, read, raw, woob, write_empty_pages, has_pages = false; 244 u64 start_off, off, len, remaining, default_len; 245 struct mtd_oob_ops io_op = {}; 246 uint user_addr = 0, npages; 247 const char *cmd = argv[0]; |
|
226 struct mtd_info *mtd; | 248 struct mtd_info *mtd; |
227 const char *cmd; 228 char *mtd_name; | 249 u32 oob_len; 250 u8 *buf; 251 int ret; |
229 | 252 |
230 /* All MTD commands need at least two arguments */ | |
231 if (argc < 2) 232 return CMD_RET_USAGE; 233 | 253 if (argc < 2) 254 return CMD_RET_USAGE; 255 |
234 /* Parse the command name and its optional suffixes */ 235 cmd = argv[1]; | 256 mtd = get_mtd_by_name(argv[1]); 257 if (IS_ERR_OR_NULL(mtd)) 258 return CMD_RET_FAILURE; |
236 | 259 |
237 /* List the MTD devices if that is what the user wants */ 238 if (strcmp(cmd, "list") == 0) 239 return do_mtd_list(); | 260 if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH) 261 has_pages = true; |
240 | 262 |
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; | 263 dump = !strncmp(cmd, "dump", 4); 264 read = dump || !strncmp(cmd, "read", 4); 265 raw = strstr(cmd, ".raw"); 266 woob = strstr(cmd, ".oob"); 267 write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); |
247 | 268 |
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; | 269 argc -= 2; 270 argv += 2; 271 272 if (!dump) { 273 if (!argc) { 274 ret = CMD_RET_USAGE; 275 goto out_put_mtd; 276 } 277 278 user_addr = simple_strtoul(argv[0], NULL, 16); 279 argc--; 280 argv++; |
255 } | 281 } |
256 put_mtd_device(mtd); | |
257 | 282 |
258 argc -= 3; 259 argv += 3; | 283 start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; 284 if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { 285 printf("Offset not aligned with a page (0x%x)\n", 286 mtd->writesize); 287 ret = CMD_RET_FAILURE; 288 goto out_put_mtd; 289 } |
260 | 290 |
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; | 291 default_len = dump ? mtd->writesize : mtd->size; 292 len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : default_len; 293 if (!mtd_is_aligned_with_min_io_size(mtd, len)) { 294 len = round_up(len, mtd->writesize); 295 printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", 296 mtd->writesize, len); 297 } |
273 | 298 |
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"); | 299 remaining = len; 300 npages = mtd_len_to_pages(mtd, len); 301 oob_len = woob ? npages * mtd->oobsize : 0; |
279 | 302 |
280 if (!dump) { 281 if (!argc) 282 return CMD_RET_USAGE; | 303 if (dump) 304 buf = kmalloc(len + oob_len, GFP_KERNEL); 305 else 306 buf = map_sysmem(user_addr, 0); |
283 | 307 |
284 user_addr = simple_strtoul(argv[0], NULL, 16); 285 argc--; 286 argv++; 287 } | 308 if (!buf) { 309 printf("Could not map/allocate the user buffer\n"); 310 ret = CMD_RET_FAILURE; 311 goto out_put_mtd; 312 } |
288 | 313 |
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 } | 314 if (has_pages) 315 printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", 316 read ? "Reading" : "Writing", len, npages, start_off, 317 raw ? " [raw]" : "", woob ? " [oob]" : "", 318 !read && write_empty_pages ? " [dontskipff]" : ""); 319 else 320 printf("%s %lld byte(s) at offset 0x%08llx\n", 321 read ? "Reading" : "Writing", len, start_off); |
295 | 322 |
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 } | 323 io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; 324 io_op.len = has_pages ? mtd->writesize : len; 325 io_op.ooblen = woob ? mtd->oobsize : 0; 326 io_op.datbuf = buf; 327 io_op.oobbuf = woob ? &buf[len] : NULL; |
304 | 328 |
305 remaining = len; 306 npages = mtd_len_to_pages(mtd, len); 307 oob_len = woob ? npages * mtd->oobsize : 0; | 329 /* Search for the first good block after the given offset */ 330 off = start_off; 331 while (mtd_block_isbad(mtd, off)) 332 off += mtd->erasesize; |
308 | 333 |
309 if (dump) 310 buf = kmalloc(len + oob_len, GFP_KERNEL); | 334 /* Loop over the pages to do the actual read/write */ 335 while (remaining) { 336 /* Skip the block if it is bad */ 337 if (mtd_is_aligned_with_block_size(mtd, off) && 338 mtd_block_isbad(mtd, off)) { 339 off += mtd->erasesize; 340 continue; 341 } 342 343 if (read) 344 ret = mtd_read_oob(mtd, off, &io_op); |
311 else | 345 else |
312 buf = map_sysmem(user_addr, 0); | 346 ret = mtd_special_write_oob(mtd, off, &io_op, 347 write_empty_pages, woob); |
313 | 348 |
314 if (!buf) { 315 printf("Could not map/allocate the user buffer\n"); 316 return CMD_RET_FAILURE; | 349 if (ret) { 350 printf("Failure while %s at offset 0x%llx\n", 351 read ? "reading" : "writing", off); 352 break; |
317 } 318 | 353 } 354 |
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); | 355 off += io_op.retlen; 356 remaining -= io_op.retlen; 357 io_op.datbuf += io_op.retlen; 358 io_op.oobbuf += io_op.oobretlen; 359 } |
327 | 360 |
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; | 361 if (!ret && dump) 362 mtd_dump_device_buf(mtd, start_off, buf, len, woob); |
333 | 363 |
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; | 364 if (dump) 365 kfree(buf); 366 else 367 unmap_sysmem(buf); |
338 | 368 |
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 } | 369 if (ret) { 370 printf("%s on %s failed with error %d\n", 371 read ? "Read" : "Write", mtd->name, ret); 372 ret = CMD_RET_FAILURE; 373 } else { 374 ret = CMD_RET_SUCCESS; 375 } |
347 | 376 |
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); | 377out_put_mtd: 378 put_mtd_device(mtd); |
354 | 379 |
355 if (ret) { 356 printf("Failure while %s at offset 0x%llx\n", 357 read ? "reading" : "writing", off); 358 return CMD_RET_FAILURE; 359 } | 380 return ret; 381} |
360 | 382 |
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 } | 383static int do_mtd_erase(cmd_tbl_t *cmdtp, int flag, int argc, 384 char * const argv[]) 385{ 386 struct erase_info erase_op = {}; 387 struct mtd_info *mtd; 388 u64 off, len; 389 bool scrub; 390 int ret; |
366 | 391 |
367 if (!ret && dump) 368 mtd_dump_device_buf(mtd, start_off, buf, len, woob); | 392 if (argc < 2) 393 return CMD_RET_USAGE; |
369 | 394 |
370 if (dump) 371 kfree(buf); 372 else 373 unmap_sysmem(buf); | 395 mtd = get_mtd_by_name(argv[1]); 396 if (IS_ERR_OR_NULL(mtd)) 397 return CMD_RET_FAILURE; |
374 | 398 |
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 } | 399 scrub = strstr(argv[0], ".dontskipbad"); |
380 | 400 |
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; | 401 argc -= 2; 402 argv += 2; |
386 | 403 |
387 off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; 388 len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size; | 404 off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; 405 len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size; |
389 | 406 |
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 } | 407 if (!mtd_is_aligned_with_block_size(mtd, off)) { 408 printf("Offset not aligned with a block (0x%x)\n", 409 mtd->erasesize); 410 ret = CMD_RET_FAILURE; 411 goto out_put_mtd; 412 } |
395 | 413 |
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 } | 414 if (!mtd_is_aligned_with_block_size(mtd, len)) { 415 printf("Size not a multiple of a block (0x%x)\n", 416 mtd->erasesize); 417 ret = CMD_RET_FAILURE; 418 goto out_put_mtd; 419 } |
401 | 420 |
402 printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", 403 off, off + len - 1, mtd_div_by_eb(len, mtd)); | 421 printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", 422 off, off + len - 1, mtd_div_by_eb(len, mtd)); |
404 | 423 |
405 erase_op.mtd = mtd; 406 erase_op.addr = off; 407 erase_op.len = len; 408 erase_op.scrub = scrub; | 424 erase_op.mtd = mtd; 425 erase_op.addr = off; 426 erase_op.len = len; 427 erase_op.scrub = scrub; |
409 | 428 |
410 while (erase_op.len) { 411 ret = mtd_erase(mtd, &erase_op); | 429 while (erase_op.len) { 430 ret = mtd_erase(mtd, &erase_op); |
412 | 431 |
413 /* Abort if its not a bad block error */ 414 if (ret != -EIO) 415 break; | 432 /* Abort if its not a bad block error */ 433 if (ret != -EIO) 434 break; |
416 | 435 |
417 printf("Skipping bad block at 0x%08llx\n", 418 erase_op.fail_addr); | 436 printf("Skipping bad block at 0x%08llx\n", erase_op.fail_addr); |
419 | 437 |
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 } | 438 /* Skip bad block and continue behind it */ 439 erase_op.len -= erase_op.fail_addr - erase_op.addr; 440 erase_op.len -= mtd->erasesize; 441 erase_op.addr = erase_op.fail_addr + mtd->erasesize; 442 } |
425 | 443 |
426 if (ret && ret != -EIO) 427 return CMD_RET_FAILURE; 428 } else if (!strcmp(cmd, "bad")) { 429 loff_t off; | 444 if (ret && ret != -EIO) 445 ret = CMD_RET_FAILURE; 446 else 447 ret = CMD_RET_SUCCESS; |
430 | 448 |
431 if (!mtd_can_have_bb(mtd)) { 432 printf("Only NAND-based devices can have bad blocks\n"); 433 return CMD_RET_SUCCESS; 434 } | 449out_put_mtd: 450 put_mtd_device(mtd); |
435 | 451 |
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 { | 452 return ret; 453} 454 455static int do_mtd_bad(cmd_tbl_t *cmdtp, int flag, int argc, 456 char * const argv[]) 457{ 458 struct mtd_info *mtd; 459 loff_t off; 460 461 if (argc < 2) |
441 return CMD_RET_USAGE; | 462 return CMD_RET_USAGE; |
463 464 mtd = get_mtd_by_name(argv[1]); 465 if (IS_ERR_OR_NULL(mtd)) 466 return CMD_RET_FAILURE; 467 468 if (!mtd_can_have_bb(mtd)) { 469 printf("Only NAND-based devices can have bad blocks\n"); 470 goto out_put_mtd; |
|
442 } 443 | 471 } 472 |
473 printf("MTD device %s bad blocks list:\n", mtd->name); 474 for (off = 0; off < mtd->size; off += mtd->erasesize) { 475 if (mtd_block_isbad(mtd, off)) 476 printf("\t0x%08llx\n", off); 477 } 478 479out_put_mtd: 480 put_mtd_device(mtd); 481 |
|
444 return CMD_RET_SUCCESS; 445} 446 | 482 return CMD_RET_SUCCESS; 483} 484 |
485#ifdef CONFIG_AUTO_COMPLETE 486static int mtd_name_complete(int argc, char * const argv[], char last_char, 487 int maxv, char *cmdv[]) 488{ 489 int len = 0, n_found = 0; 490 struct mtd_info *mtd; 491 492 argc--; 493 argv++; 494 495 if (argc > 1 || 496 (argc == 1 && (last_char == '\0' || isblank(last_char)))) 497 return 0; 498 499 if (argc) 500 len = strlen(argv[0]); 501 502 mtd_for_each_device(mtd) { 503 if (argc && 504 (len > strlen(mtd->name) || 505 strncmp(argv[0], mtd->name, len))) 506 continue; 507 508 if (n_found >= maxv - 2) { 509 cmdv[n_found++] = "..."; 510 break; 511 } 512 513 cmdv[n_found++] = mtd->name; 514 } 515 516 cmdv[n_found] = NULL; 517 518 return n_found; 519} 520#endif /* CONFIG_AUTO_COMPLETE */ 521 |
|
447static 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" --- 10 unchanged lines hidden (view full) --- 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 | 522static char mtd_help_text[] = 523#ifdef CONFIG_SYS_LONGHELP 524 "- generic operations on memory technology devices\n\n" 525 "mtd list\n" 526 "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" 527 "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" 528 "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" 529 "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" --- 10 unchanged lines hidden (view full) --- 540 "\t<size>: length of the operation in bytes (default: the entire device)\n" 541 "\t\t* must be a multiple of a block for erase\n" 542 "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" 543 "\n" 544 "The .dontskipff option forces writing empty pages, don't use it if unsure.\n" 545#endif 546 ""; 547 |
473U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text); | 548U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, 549 U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list), 550 U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io, 551 mtd_name_complete), 552 U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io, 553 mtd_name_complete), 554 U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io, 555 mtd_name_complete), 556 U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, 557 mtd_name_complete), 558 U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, 559 mtd_name_complete)); |