changeset 2993:56ce19492892

redboot NAND: add `nand badblocks' subcommand
author Ross Younger <wry@ecoscentric.com>
date Tue, 17 Nov 2009 16:12:45 +0000
parents d0daa15943a5
children 3d3b0d53c2e5
files packages/redboot/current/ChangeLog packages/redboot/current/cdl/redboot.cdl packages/redboot/current/src/nand.c
diffstat 3 files changed, 330 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/packages/redboot/current/ChangeLog
+++ b/packages/redboot/current/ChangeLog
@@ -1,3 +1,11 @@
+2009-11-17  Ross Younger  <wry@ecoscentric.com>
+
+	* src/nand.c: Add "nand badblocks" subcommand and nested
+	subsubcmds: summarise or list bad blocks, and manually
+	change the state of a block's marking.
+	* cdl/redboot.cdl: Add CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD and
+	CYGSEM_REDBOOT_NAND_BADBLOCKS_MARK_CMD.
+
 2009-06-26  Ross Younger  <wry@ecoscentric.com>
 
 	* src/nand.c: Created for NAND utilities
--- a/packages/redboot/current/cdl/redboot.cdl
+++ b/packages/redboot/current/cdl/redboot.cdl
@@ -1347,6 +1347,31 @@ cdl_package CYGPKG_REDBOOT {
                     your users with this ability.
                 "
             }
+
+            cdl_option CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD {
+                display     "Include support for nand badblocks command"
+                flavor      bool
+                default_value   1
+                active_if   CYGPKG_REDBOOT_NAND
+                description "
+                    This option provides a helper command to query
+                    and (optionally) change the state of bad blocks
+                    in the NAND device's Bad Blocks Table.
+                "
+            }
+
+            cdl_option CYGSEM_REDBOOT_NAND_BADBLOCKS_MARK_CMD {
+                display     "Can nand badblocks change block states"
+                flavor      bool
+                default_value   1
+                active_if   CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD
+                description "
+                    This option enables the `badblocks mark' subcommand,
+                    which allows the user to manually change the state
+                    of blocks in the Bad Block Table. It is potentially
+                    dangerous if misused, so you may not wish to
+                    enable this."
+            }
         }
 
     }
--- a/packages/redboot/current/src/nand.c
+++ b/packages/redboot/current/src/nand.c
@@ -64,6 +64,16 @@ CYG_HAL_TABLE_END( __NAND_cmds_TAB_END__
 
 extern struct cmd __NAND_cmds_TAB__[], __NAND_cmds_TAB_END__;
 
+#ifdef CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD
+static unsigned count_nand_devices(void)
+{
+    unsigned n = 0;
+    cyg_nand_device *nand;
+    for (nand = &cyg_nanddevtab[0]; nand != &cyg_nanddevtab_end; nand++,n++);
+    return n;
+}
+#endif
+
 //==========================================================================
 
 static void
@@ -208,6 +218,293 @@ static void nand_info(int argc, char*arg
 local_cmd_entry("info", "Displays information about one or more NAND devices", "[<device>] [<device>...]", nand_info, NAND_cmds);
 
 //==========================================================================
+#ifdef CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD
+// nand badblocks [-d <device>] SUBCOMMAND - bad block info and management
+// Shared handling allows -d device to default to the single present device.
+
+CYG_HAL_TABLE_BEGIN( __NAND_badblocks_cmds_TAB__, NAND_badblocks_cmds);
+CYG_HAL_TABLE_END( __NAND_badblocks_cmds_TAB_END__, NAND_badblocks_cmds);
+
+extern struct cmd __NAND_badblocks_cmds_TAB__[],
+                  __NAND_badblocks_cmds_TAB_END__;
+
+static cmd_fun do_nand_badblocks;
+static cmd_fun nbb_subcommand;
+
+#define nbb_cmd(_w, _h, _u) \
+    static struct cmd _cmd_tab_nbb_subcommand_##_w CYG_HAL_TABLE_QUALIFIED_ENTRY(NAND_badblocks_cmds,nbb_subcommand) = { #_w, _h, "[-d <device>] " _u, nbb_subcommand, 0, 0 }
+
+struct cmd _cmd_tag_do_nand_badblocks CYG_HAL_TABLE_QUALIFIED_ENTRY(NAND_cmds, do_nand_badblocks) = {
+    "badblocks", "Interrogates and manages the device bad block table",
+    "<subcommand> [-d <device>] [<subcommand args>...]",
+    do_nand_badblocks,
+    __NAND_badblocks_cmds_TAB__, &__NAND_badblocks_cmds_TAB_END__
+};
+
+static void nbb_usage(const char *why)
+{
+    diag_printf("*** invalid `nand badblocks' subcommand: %s\n", why);
+    cmd_usage(__NAND_badblocks_cmds_TAB__,
+            &__NAND_badblocks_cmds_TAB_END__, "nand badblocks ");
+}
+
+static void do_nand_badblocks(int argc, char**argv)
+{
+    struct cmd *cmd;
+    if (argc<2) {
+        // defaulting behaviour
+        nbb_subcommand(argc,argv);
+        return;
+    }
+    if ((cmd = cmd_search(__NAND_badblocks_cmds_TAB__,
+                    &__NAND_badblocks_cmds_TAB_END__, argv[1]))
+            != (struct cmd *)0) {
+        (cmd->fun)(argc, argv);
+        return;
+    }
+    nbb_usage("unrecognized command");
+}
+
+static void nbb_help(int argc, char**argv)
+{
+    cmd_usage(__NAND_badblocks_cmds_TAB__,
+            &__NAND_badblocks_cmds_TAB_END__, "nand badblocks ");
+}
+
+local_cmd_entry("help", "Explains the available commands", "", nbb_help, NAND_badblocks_cmds);
+
+static struct {
+    cyg_nand_bbt_status_t state;
+    const char *msg;
+} states[5] = {
+    { CYG_NAND_BBT_OK, "OK" },
+    { CYG_NAND_BBT_WORNBAD, "worn bad" },
+    { CYG_NAND_BBT_RESERVED, "reserved" },
+    { CYG_NAND_BBT_FACTORY_BAD, "factory bad" },
+    { 0,0 }
+};
+
+static void nbb_states(int argc, char**argv)
+{
+    int i = 0;
+    diag_printf("The possible block states are:\n");
+    while (states[i].msg) {
+        diag_printf("\t%u : %s\n", states[i].state, states[i].msg);
+        ++i;
+    }
+}
+
+local_cmd_entry("states", "Lists the valid states in the BBT", "", nbb_states, NAND_badblocks_cmds);
+
+// .........................
+
+// nand badblocks SUMMARY and nand badblocks LIST:
+// count the number of blocks of a given type and (in list mode) dump them out.
+
+// Forbidden knowledge:
+extern int cyg_nand_bbti_query(cyg_nand_device *dev, cyg_nand_block_addr blk);
+
+static void do_summary(cyg_nand_device *nand)
+{
+    // count the bad blocks
+    cyg_nand_block_addr i = 0;
+    unsigned state[1+CYG_NAND_BBT_FACTORY_BAD];
+    int rv;
+    memset(state, 0, sizeof(state));
+    for (i=0; i < NAND_BLOCKCOUNT(nand); i++) {
+        rv = cyg_nand_bbti_query(nand,i);
+        if (rv>=CYG_NAND_BBT_OK && rv <= CYG_NAND_BBT_FACTORY_BAD)
+            ++state[rv];
+    }
+
+    diag_printf("Device `%s' has %u blocks", nand->devname, NAND_BLOCKCOUNT(nand));
+
+    int counted=0;
+#define REPORT(_s,_msg) do {                                        \
+    if (state[_s] != 0) {                                           \
+        if (counted)                                                \
+            diag_printf(", %u %s", state[_s], _msg);                \
+        else                                                        \
+            diag_printf(", of which %u are %s", state[_s], _msg);   \
+        counted += state[_s];                                       \
+    }                                                               \
+} while(0)
+
+    // Report in sensible order, not enum order.
+    REPORT(CYG_NAND_BBT_FACTORY_BAD, "factory bad");
+    REPORT(CYG_NAND_BBT_WORNBAD, "worn bad");
+    REPORT(CYG_NAND_BBT_RESERVED, "reserved");
+
+    int pct = 100 * (NAND_BLOCKCOUNT(nand) - counted) / NAND_BLOCKCOUNT(nand);
+    diag_printf(". %u blocks (%d%%) are OK.\n", NAND_BLOCKCOUNT(nand) - counted, pct);
+}
+
+nbb_cmd(summary, "Summarises the bad blocks table", "");
+
+// .........................
+
+static void do_sublist(cyg_nand_device *nand, cyg_nand_bbt_status_t st, const char *msg)
+{
+    cyg_nand_block_addr i = 0;
+    int rv;
+    unsigned found = 0;
+
+    diag_printf("%s: ", msg);
+    for (i=0; i < NAND_BLOCKCOUNT(nand); i++) {
+        rv = cyg_nand_bbti_query(nand,i);
+        if (rv == st)  {
+            diag_printf("%s%d", (found ? ", " : ""), i);
+            ++found;
+        }
+    }
+
+    if (found)
+        diag_printf(". (count: %d blocks)\n", found);
+    else
+        diag_printf("no blocks\n");
+}
+
+static void do_list(cyg_nand_device *nand) {
+    do_sublist(nand, CYG_NAND_BBT_FACTORY_BAD, "factory bad");
+    do_sublist(nand, CYG_NAND_BBT_WORNBAD, "worn bad");
+    do_sublist(nand, CYG_NAND_BBT_RESERVED, "reserved");
+}
+
+nbb_cmd(list, "Lists the contents of the bad blocks table", "");
+
+// .........................
+
+// nand badblocks mark -d device -b block -s state
+// Manually marks a block in the BBT. Use with caution!
+#ifdef CYGSEM_REDBOOT_NAND_BADBLOCKS_MARK_CMD
+// more forbidden knowledge:
+extern int cyg_nand_bbti_markany(cyg_nand_device *dev, cyg_nand_block_addr blk, cyg_nand_bbt_status_t st);
+
+static void nbb_mark_usage(void) {
+    diag_printf("Usage: nand badblocks mark [-d device] -b block -s state\n  The valid states are shown by `nand badblocks states'.\n");
+}
+
+static void do_mark(cyg_nand_device *nand, int markblock, int markstate) 
+{
+    int bail=0;
+    if (markblock < 0 || markblock > NAND_BLOCKCOUNT(nand)) {
+        diag_printf("Invalid block number.\n");
+        bail=1;
+    }
+    if (markstate < CYG_NAND_BBT_OK || markstate > CYG_NAND_BBT_FACTORY_BAD) {
+        diag_printf("Invalid state.\n");
+        bail=1;
+    }
+    if (markstate == CYG_NAND_BBT_RESERVED) {
+        diag_printf("State `reserved' is for the BBT itself and cannot be written to flash.\n");
+        bail=1;
+    }
+    if (bail) {
+        nbb_mark_usage();
+        return;
+    }
+
+
+    // We don't need to take the devlock as RedBoot is single-threaded.
+    int rv = cyg_nand_bbti_markany(nand, markblock, markstate);
+    if (rv != 0)
+        diag_printf("mark operation failed with status %d\n", rv);
+    else
+        diag_printf("OK\n");
+}
+
+nbb_cmd(mark, "Manually marks a block in the BBT", "-b block -s state");
+
+#endif
+
+// .........................
+
+static void nbb_subcommand(int argc, char*argv[])
+{
+    cyg_nand_device *nand = 0;
+    const char *subcmd = argv[1];
+    char *dev;
+    cyg_bool got_dev = false;
+#define MAXOPTS 4
+    struct option_info opts[MAXOPTS];
+    enum {
+        unset=0,
+        dodefault,
+        summary,
+        list,
+        mark
+    } mode = unset;
+    int markblock = -1, markstate = -1;
+
+    // we know that we _have_ a valid subcommand at this point, just not _which_.
+    int n_opts = 0;
+#define OPTION(_f, _a, _t, _p, _got, _arg) init_opts(&opts[n_opts++], _f, _a, _t, _p, _got, _arg)
+    OPTION('d', true, OPTION_ARG_TYPE_STR, (void*)&dev, &got_dev, "device");
+
+    if (subcmd) {
+        int sclen = strlen(subcmd);
+        if (0==strncmp(subcmd, "summary", sclen))
+            mode = summary;
+        else if (0==strncmp(subcmd, "list", sclen))
+            mode = list;
+        else if (0==strncmp(subcmd, "mark", sclen)) {
+            mode = mark;
+            OPTION('s', true, OPTION_ARG_TYPE_NUM, (void*) &markstate, 0, "state");
+            OPTION('b', true, OPTION_ARG_TYPE_NUM, (void*) &markblock, 0, "block");
+        }
+
+        // Caution! If you are adding a new subsubcommand with options, make sure that n_opts can't become bigger than MAXOPTS !
+    }
+    else
+        mode = dodefault;
+
+    if (n_opts > MAXOPTS) {
+        diag_printf("BUG: Too many options at %s:%d\n",__FILE__,__LINE__);
+        return;
+    }
+
+    if (!scan_opts(argc, argv, 2, opts, n_opts, 0, 0, ""))
+        return;
+
+    if (got_dev) {
+        cyg_nand_lookup(dev, &nand);
+        if (!nand)
+            diag_printf("Device %s not found.\n", dev);
+    } else {
+        if (count_nand_devices()==1)
+            cyg_nand_lookup(cyg_nanddevtab[0].devname, &nand);
+        else
+            diag_printf("More than one NAND device present, please specify with -d <device>\n");
+    }
+    if (!nand) return;
+
+    switch(mode) {
+        case dodefault:
+            diag_printf("(You didn't specify a subcommand, so I'm defaulting to `summary.'\n `nand badblocks help' lists all known subcommands.)\n");
+            // FALLTHROUGH
+        case summary:
+            do_summary(nand);
+            return;
+        case list:
+            do_list(nand);
+            return;
+#ifdef CYGSEM_REDBOOT_NAND_BADBLOCKS_MARK_CMD
+        case mark:
+            if (markblock==-1 || markstate==-1) {
+                nbb_mark_usage();
+            } else
+                do_mark(nand, markblock, markstate);
+            return;
+#endif
+        case unset:
+            diag_printf("can't happen at %s %d\n", __FILE__, __LINE__);
+            return;
+    }
+}
+
+#endif // CYGSEM_REDBOOT_NAND_BADBLOCKS_CMD
+
+//==========================================================================
 // nand erase - erases a nand partition. (Who'd have thunk?)
 
 #ifdef CYGSEM_REDBOOT_NAND_ERASE_CMD