Merge https://gitlab.denx.de/u-boot/custodians/u-boot-marvell
[oweals/u-boot.git] / cmd / mtd.c
index 6142223984672a1dc1afa457dcdae0d12b2065c9..f407c5e4450ac438b0b62c685c37adbac99e14a8 100644 (file)
--- a/cmd/mtd.c
+++ b/cmd/mtd.c
 #include <malloc.h>
 #include <mapmem.h>
 #include <mtd.h>
+#include <dm/devres.h>
+#include <linux/err.h>
+
+#include <linux/ctype.h>
+
+static struct mtd_info *get_mtd_by_name(const char *name)
+{
+       struct mtd_info *mtd;
+
+       mtd_probe_devices();
+
+       mtd = get_mtd_device_nm(name);
+       if (IS_ERR_OR_NULL(mtd))
+               printf("MTD device %s not found, ret %ld\n", name,
+                      PTR_ERR(mtd));
+
+       return mtd;
+}
 
 static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len)
 {
@@ -177,7 +195,8 @@ static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op)
        return true;
 }
 
-static int do_mtd_list(void)
+static int do_mtd_list(cmd_tbl_t *cmdtp, int flag, int argc,
+                      char * const argv[])
 {
        struct mtd_info *mtd;
        int dev_nb = 0;
@@ -221,231 +240,289 @@ static int mtd_special_write_oob(struct mtd_info *mtd, u64 off,
        return ret;
 }
 
-static int do_mtd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+static int do_mtd_io(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 {
+       bool dump, read, raw, woob, write_empty_pages, has_pages = false;
+       u64 start_off, off, len, remaining, default_len;
+       struct mtd_oob_ops io_op = {};
+       uint user_addr = 0, npages;
+       const char *cmd = argv[0];
        struct mtd_info *mtd;
-       const char *cmd;
-       char *mtd_name;
+       u32 oob_len;
+       u8 *buf;
+       int ret;
 
-       /* All MTD commands need at least two arguments */
        if (argc < 2)
                return CMD_RET_USAGE;
 
-       /* Parse the command name and its optional suffixes */
-       cmd = argv[1];
+       mtd = get_mtd_by_name(argv[1]);
+       if (IS_ERR_OR_NULL(mtd))
+               return CMD_RET_FAILURE;
 
-       /* List the MTD devices if that is what the user wants */
-       if (strcmp(cmd, "list") == 0)
-               return do_mtd_list();
+       if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH)
+               has_pages = true;
 
-       /*
-        * The remaining commands require also at least a device ID.
-        * Check the selected device is valid. Ensure it is probed.
-        */
-       if (argc < 3)
-               return CMD_RET_USAGE;
+       dump = !strncmp(cmd, "dump", 4);
+       read = dump || !strncmp(cmd, "read", 4);
+       raw = strstr(cmd, ".raw");
+       woob = strstr(cmd, ".oob");
+       write_empty_pages = !has_pages || strstr(cmd, ".dontskipff");
 
-       mtd_name = argv[2];
-       mtd_probe_devices();
-       mtd = get_mtd_device_nm(mtd_name);
-       if (IS_ERR_OR_NULL(mtd)) {
-               printf("MTD device %s not found, ret %ld\n",
-                      mtd_name, PTR_ERR(mtd));
-               return CMD_RET_FAILURE;
-       }
-       put_mtd_device(mtd);
+       argc -= 2;
+       argv += 2;
 
-       argc -= 3;
-       argv += 3;
-
-       /* Do the parsing */
-       if (!strncmp(cmd, "read", 4) || !strncmp(cmd, "dump", 4) ||
-           !strncmp(cmd, "write", 5)) {
-               bool has_pages = mtd->type == MTD_NANDFLASH ||
-                                mtd->type == MTD_MLCNANDFLASH;
-               bool dump, read, raw, woob, write_empty_pages;
-               struct mtd_oob_ops io_op = {};
-               uint user_addr = 0, npages;
-               u64 start_off, off, len, remaining, default_len;
-               u32 oob_len;
-               u8 *buf;
-               int ret;
-
-               dump = !strncmp(cmd, "dump", 4);
-               read = dump || !strncmp(cmd, "read", 4);
-               raw = strstr(cmd, ".raw");
-               woob = strstr(cmd, ".oob");
-               write_empty_pages = !has_pages || strstr(cmd, ".dontskipff");
-
-               if (!dump) {
-                       if (!argc)
-                               return CMD_RET_USAGE;
-
-                       user_addr = simple_strtoul(argv[0], NULL, 16);
-                       argc--;
-                       argv++;
+       if (!dump) {
+               if (!argc) {
+                       ret = CMD_RET_USAGE;
+                       goto out_put_mtd;
                }
 
-               start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
-               if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) {
-                       printf("Offset not aligned with a page (0x%x)\n",
-                              mtd->writesize);
-                       return CMD_RET_FAILURE;
-               }
+               user_addr = simple_strtoul(argv[0], NULL, 16);
+               argc--;
+               argv++;
+       }
 
-               default_len = dump ? mtd->writesize : mtd->size;
-               len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) :
-                                default_len;
-               if (!mtd_is_aligned_with_min_io_size(mtd, len)) {
-                       len = round_up(len, mtd->writesize);
-                       printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n",
-                              mtd->writesize, len);
-               }
+       start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
+       if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) {
+               printf("Offset not aligned with a page (0x%x)\n",
+                      mtd->writesize);
+               ret = CMD_RET_FAILURE;
+               goto out_put_mtd;
+       }
 
-               remaining = len;
-               npages = mtd_len_to_pages(mtd, len);
-               oob_len = woob ? npages * mtd->oobsize : 0;
+       default_len = dump ? mtd->writesize : mtd->size;
+       len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : default_len;
+       if (!mtd_is_aligned_with_min_io_size(mtd, len)) {
+               len = round_up(len, mtd->writesize);
+               printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n",
+                      mtd->writesize, len);
+       }
 
-               if (dump)
-                       buf = kmalloc(len + oob_len, GFP_KERNEL);
-               else
-                       buf = map_sysmem(user_addr, 0);
+       remaining = len;
+       npages = mtd_len_to_pages(mtd, len);
+       oob_len = woob ? npages * mtd->oobsize : 0;
+
+       if (dump)
+               buf = kmalloc(len + oob_len, GFP_KERNEL);
+       else
+               buf = map_sysmem(user_addr, 0);
 
-               if (!buf) {
-                       printf("Could not map/allocate the user buffer\n");
-                       return CMD_RET_FAILURE;
+       if (!buf) {
+               printf("Could not map/allocate the user buffer\n");
+               ret = CMD_RET_FAILURE;
+               goto out_put_mtd;
+       }
+
+       if (has_pages)
+               printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n",
+                      read ? "Reading" : "Writing", len, npages, start_off,
+                      raw ? " [raw]" : "", woob ? " [oob]" : "",
+                      !read && write_empty_pages ? " [dontskipff]" : "");
+       else
+               printf("%s %lld byte(s) at offset 0x%08llx\n",
+                      read ? "Reading" : "Writing", len, start_off);
+
+       io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
+       io_op.len = has_pages ? mtd->writesize : len;
+       io_op.ooblen = woob ? mtd->oobsize : 0;
+       io_op.datbuf = buf;
+       io_op.oobbuf = woob ? &buf[len] : NULL;
+
+       /* Search for the first good block after the given offset */
+       off = start_off;
+       while (mtd_block_isbad(mtd, off))
+               off += mtd->erasesize;
+
+       /* Loop over the pages to do the actual read/write */
+       while (remaining) {
+               /* Skip the block if it is bad */
+               if (mtd_is_aligned_with_block_size(mtd, off) &&
+                   mtd_block_isbad(mtd, off)) {
+                       off += mtd->erasesize;
+                       continue;
                }
 
-               if (has_pages)
-                       printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n",
-                              read ? "Reading" : "Writing", len, npages, start_off,
-                              raw ? " [raw]" : "", woob ? " [oob]" : "",
-                              !read && write_empty_pages ? " [dontskipff]" : "");
+               if (read)
+                       ret = mtd_read_oob(mtd, off, &io_op);
                else
-                       printf("%s %lld byte(s) at offset 0x%08llx\n",
-                              read ? "Reading" : "Writing", len, start_off);
-
-               io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB;
-               io_op.len = has_pages ? mtd->writesize : len;
-               io_op.ooblen = woob ? mtd->oobsize : 0;
-               io_op.datbuf = buf;
-               io_op.oobbuf = woob ? &buf[len] : NULL;
-
-               /* Search for the first good block after the given offset */
-               off = start_off;
-               while (mtd_block_isbad(mtd, off))
-                       off += mtd->erasesize;
+                       ret = mtd_special_write_oob(mtd, off, &io_op,
+                                                   write_empty_pages, woob);
 
-               /* Loop over the pages to do the actual read/write */
-               while (remaining) {
-                       /* Skip the block if it is bad */
-                       if (mtd_is_aligned_with_block_size(mtd, off) &&
-                           mtd_block_isbad(mtd, off)) {
-                               off += mtd->erasesize;
-                               continue;
-                       }
+               if (ret) {
+                       printf("Failure while %s at offset 0x%llx\n",
+                              read ? "reading" : "writing", off);
+                       break;
+               }
 
-                       if (read)
-                               ret = mtd_read_oob(mtd, off, &io_op);
-                       else
-                               ret = mtd_special_write_oob(mtd, off, &io_op,
-                                                           write_empty_pages,
-                                                           woob);
-
-                       if (ret) {
-                               printf("Failure while %s at offset 0x%llx\n",
-                                      read ? "reading" : "writing", off);
-                               return CMD_RET_FAILURE;
-                       }
+               off += io_op.retlen;
+               remaining -= io_op.retlen;
+               io_op.datbuf += io_op.retlen;
+               io_op.oobbuf += io_op.oobretlen;
+       }
 
-                       off += io_op.retlen;
-                       remaining -= io_op.retlen;
-                       io_op.datbuf += io_op.retlen;
-                       io_op.oobbuf += io_op.oobretlen;
-               }
+       if (!ret && dump)
+               mtd_dump_device_buf(mtd, start_off, buf, len, woob);
 
-               if (!ret && dump)
-                       mtd_dump_device_buf(mtd, start_off, buf, len, woob);
+       if (dump)
+               kfree(buf);
+       else
+               unmap_sysmem(buf);
 
-               if (dump)
-                       kfree(buf);
-               else
-                       unmap_sysmem(buf);
+       if (ret) {
+               printf("%s on %s failed with error %d\n",
+                      read ? "Read" : "Write", mtd->name, ret);
+               ret = CMD_RET_FAILURE;
+       } else {
+               ret = CMD_RET_SUCCESS;
+       }
 
-               if (ret) {
-                       printf("%s on %s failed with error %d\n",
-                              read ? "Read" : "Write", mtd->name, ret);
-                       return CMD_RET_FAILURE;
-               }
+out_put_mtd:
+       put_mtd_device(mtd);
 
-       } else if (!strcmp(cmd, "erase")) {
-               bool scrub = strstr(cmd, ".dontskipbad");
-               struct erase_info erase_op = {};
-               u64 off, len;
-               int ret;
+       return ret;
+}
 
-               off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
-               len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size;
+static int do_mtd_erase(cmd_tbl_t *cmdtp, int flag, int argc,
+                       char * const argv[])
+{
+       struct erase_info erase_op = {};
+       struct mtd_info *mtd;
+       u64 off, len;
+       bool scrub;
+       int ret = 0;
 
-               if (!mtd_is_aligned_with_block_size(mtd, off)) {
-                       printf("Offset not aligned with a block (0x%x)\n",
-                              mtd->erasesize);
-                       return CMD_RET_FAILURE;
-               }
+       if (argc < 2)
+               return CMD_RET_USAGE;
 
-               if (!mtd_is_aligned_with_block_size(mtd, len)) {
-                       printf("Size not a multiple of a block (0x%x)\n",
-                              mtd->erasesize);
-                       return CMD_RET_FAILURE;
-               }
+       mtd = get_mtd_by_name(argv[1]);
+       if (IS_ERR_OR_NULL(mtd))
+               return CMD_RET_FAILURE;
+
+       scrub = strstr(argv[0], ".dontskipbad");
+
+       argc -= 2;
+       argv += 2;
+
+       off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0;
+       len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size;
+
+       if (!mtd_is_aligned_with_block_size(mtd, off)) {
+               printf("Offset not aligned with a block (0x%x)\n",
+                      mtd->erasesize);
+               ret = CMD_RET_FAILURE;
+               goto out_put_mtd;
+       }
+
+       if (!mtd_is_aligned_with_block_size(mtd, len)) {
+               printf("Size not a multiple of a block (0x%x)\n",
+                      mtd->erasesize);
+               ret = CMD_RET_FAILURE;
+               goto out_put_mtd;
+       }
 
-               printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n",
-                      off, off + len - 1, mtd_div_by_eb(len, mtd));
+       printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n",
+              off, off + len - 1, mtd_div_by_eb(len, mtd));
 
-               erase_op.mtd = mtd;
-               erase_op.addr = off;
-               erase_op.len = len;
-               erase_op.scrub = scrub;
+       erase_op.mtd = mtd;
+       erase_op.addr = off;
+       erase_op.len = mtd->erasesize;
+       erase_op.scrub = scrub;
 
-               while (erase_op.len) {
-                       ret = mtd_erase(mtd, &erase_op);
+       while (len) {
+               ret = mtd_erase(mtd, &erase_op);
 
+               if (ret) {
                        /* Abort if its not a bad block error */
                        if (ret != -EIO)
                                break;
-
                        printf("Skipping bad block at 0x%08llx\n",
-                              erase_op.fail_addr);
-
-                       /* Skip bad block and continue behind it */
-                       erase_op.len -= erase_op.fail_addr - erase_op.addr;
-                       erase_op.len -= mtd->erasesize;
-                       erase_op.addr = erase_op.fail_addr + mtd->erasesize;
+                              erase_op.addr);
                }
 
-               if (ret && ret != -EIO)
-                       return CMD_RET_FAILURE;
-       } else if (!strcmp(cmd, "bad")) {
-               loff_t off;
+               len -= mtd->erasesize;
+               erase_op.addr += mtd->erasesize;
+       }
 
-               if (!mtd_can_have_bb(mtd)) {
-                       printf("Only NAND-based devices can have bad blocks\n");
-                       return CMD_RET_SUCCESS;
-               }
+       if (ret && ret != -EIO)
+               ret = CMD_RET_FAILURE;
+       else
+               ret = CMD_RET_SUCCESS;
 
-               printf("MTD device %s bad blocks list:\n", mtd->name);
-               for (off = 0; off < mtd->size; off += mtd->erasesize)
-                       if (mtd_block_isbad(mtd, off))
-                               printf("\t0x%08llx\n", off);
-       } else {
+out_put_mtd:
+       put_mtd_device(mtd);
+
+       return ret;
+}
+
+static int do_mtd_bad(cmd_tbl_t *cmdtp, int flag, int argc,
+                     char * const argv[])
+{
+       struct mtd_info *mtd;
+       loff_t off;
+
+       if (argc < 2)
                return CMD_RET_USAGE;
+
+       mtd = get_mtd_by_name(argv[1]);
+       if (IS_ERR_OR_NULL(mtd))
+               return CMD_RET_FAILURE;
+
+       if (!mtd_can_have_bb(mtd)) {
+               printf("Only NAND-based devices can have bad blocks\n");
+               goto out_put_mtd;
        }
 
+       printf("MTD device %s bad blocks list:\n", mtd->name);
+       for (off = 0; off < mtd->size; off += mtd->erasesize) {
+               if (mtd_block_isbad(mtd, off))
+                       printf("\t0x%08llx\n", off);
+       }
+
+out_put_mtd:
+       put_mtd_device(mtd);
+
        return CMD_RET_SUCCESS;
 }
 
-static char mtd_help_text[] =
+#ifdef CONFIG_AUTO_COMPLETE
+static int mtd_name_complete(int argc, char * const argv[], char last_char,
+                            int maxv, char *cmdv[])
+{
+       int len = 0, n_found = 0;
+       struct mtd_info *mtd;
+
+       argc--;
+       argv++;
+
+       if (argc > 1 ||
+           (argc == 1 && (last_char == '\0' || isblank(last_char))))
+               return 0;
+
+       if (argc)
+               len = strlen(argv[0]);
+
+       mtd_for_each_device(mtd) {
+               if (argc &&
+                   (len > strlen(mtd->name) ||
+                    strncmp(argv[0], mtd->name, len)))
+                       continue;
+
+               if (n_found >= maxv - 2) {
+                       cmdv[n_found++] = "...";
+                       break;
+               }
+
+               cmdv[n_found++] = mtd->name;
+       }
+
+       cmdv[n_found] = NULL;
+
+       return n_found;
+}
+#endif /* CONFIG_AUTO_COMPLETE */
+
 #ifdef CONFIG_SYS_LONGHELP
+static char mtd_help_text[] =
        "- generic operations on memory technology devices\n\n"
        "mtd list\n"
        "mtd read[.raw][.oob]                  <name> <addr> [<off> [<size>]]\n"
@@ -466,8 +543,18 @@ static char mtd_help_text[] =
        "\t\t* must be a multiple of a block for erase\n"
        "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n"
        "\n"
-       "The .dontskipff option forces writing empty pages, don't use it if unsure.\n"
+       "The .dontskipff option forces writing empty pages, don't use it if unsure.\n";
 #endif
-       "";
 
-U_BOOT_CMD(mtd, 10, 1, do_mtd, "MTD utils", mtd_help_text);
+U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text,
+               U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list),
+               U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io,
+                                            mtd_name_complete),
+               U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io,
+                                            mtd_name_complete),
+               U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io,
+                                            mtd_name_complete),
+               U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase,
+                                            mtd_name_complete),
+               U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad,
+                                            mtd_name_complete));