blob: 22c1022459816a9884901fba3cf5ea4e70c52a17 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <getopt.h>
#include <stdio.h>
#include "aml.h"
#include "commands.h"
#include "ftl.h"
#include "nand-broker.h"
namespace {
constexpr char kUsageMessage[] = R"""(
Low level access tool for a NAND device.
WARNING: This tool may overwrite the NAND device.
nand-util --device /dev/sys/platform/05:00:d/aml-raw_nand/nand/broker --info
Note that to use this tool either the driver binding rules have to be adjusted
so that the broker driver is loaded for the desired NAND device, or at least the
NAND device should not be bound to any other driver (like an FTL, skip-block or
or nandpart). This tool will attempt to load a broker driver if the device path
doesn't end with "/broker".
Options:
--device (-d) path : Specifies the broker device to use.
--info (-i) : Show basic NAND information.
--bbt (-t) : Display bad block info.
--read (-r) --absolute xxx : Read the page number xxx.
--erase (-e) --block xxx --count yyy: Erase yyy blocks starting at xxx.
--check (-c) : Looks for read errors on the device.
--save (-s) --block xxx --file path: Save the block xxx to path.
--wear : print wear counts.
--file (-f) path: Path to use when saving data.
--absolute (-a) xxx : Use an absolute page number.
--page (-p) xxx : Use the xxx page number (from within a block).
--block (-b) xxx : Use the xxx block number (0-based).
--count (-n) xxx : Limit the operation to xxx blocks.
Only supported with --check, --erase and --save.
--no-ftl : Don't attempt to interpret FTL data.
--live-dangerously : Don't prompt for confirmation.
)""";
enum class Actions {
kBbt,
kRead,
kErase,
kReadCheck,
kSave,
kWear
};
// Configuration info (what to do).
struct Config {
const char* path;
const char* file;
uint32_t page_num;
uint32_t block_num;
uint32_t abs_page;
uint32_t count;
Actions action;
int num_actions;
bool info;
bool skip_prompt;
bool ignore_ftl;
};
bool GetOptions(int argc, char** argv, Config* config) {
while (true) {
struct option options[] = {
{"device", required_argument, nullptr, 'd'},
{"info", no_argument, nullptr, 'i'},
{"bbt", no_argument, nullptr, 't'},
{"read", no_argument, nullptr, 'r'},
{"erase", no_argument, nullptr, 'e'},
{"check", no_argument, nullptr, 'c'},
{"save", no_argument, nullptr, 's'},
{"wear", no_argument, nullptr, 'x'},
{"file", required_argument, nullptr, 'f'},
{"page", required_argument, nullptr, 'p'},
{"block", required_argument, nullptr, 'b'},
{"absolute", required_argument, nullptr, 'a'},
{"count", required_argument, nullptr, 'n'},
{"no-ftl", no_argument, nullptr, 'z'},
{"live-dangerously", no_argument, nullptr, 'y'},
{"help", no_argument, nullptr, 'h'},
{nullptr, 0, nullptr, 0},
};
int opt_index;
int c = getopt_long(argc, argv, "d:irtecsf:p:b:a:n:h", options, &opt_index);
if (c < 0) {
break;
}
switch (c) {
case 'd':
config->path = optarg;
break;
case 'i':
config->info = true;
break;
case 't':
config->action = Actions::kBbt;
config->num_actions++;
break;
case 'r':
config->action = Actions::kRead;
config->num_actions++;
break;
case 'e':
config->action = Actions::kErase;
config->num_actions++;
break;
case 'c':
config->action = Actions::kReadCheck;
config->num_actions++;
break;
case 's':
config->action = Actions::kSave;
config->num_actions++;
break;
case 'x':
config->action = Actions::kWear;
config->num_actions++;
break;
case 'f':
config->file = optarg;
break;
case 'p':
config->page_num = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
break;
case 'b':
config->block_num = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
break;
case 'a':
config->abs_page = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
break;
case 'n':
config->count = static_cast<uint32_t>(strtoul(optarg, NULL, 0));
break;
case 'z':
config->ignore_ftl = true;
break;
case 'y':
config->skip_prompt = true;
break;
case 'h':
printf("%s\n", kUsageMessage);
return 0;
}
}
return argc == optind;
}
bool ValidateOptions(const Config& config) {
if (!config.path) {
printf("Device needed\n");
printf("%s\n", kUsageMessage);
return false;
}
if (config.num_actions > 1) {
printf("Only one action allowed\n");
return false;
}
if (config.abs_page && config.page_num) {
printf("Provide either a block + page or an absolute page number\n");
return false;
}
if ((config.action == Actions::kErase || config.action == Actions::kSave) &&
(config.page_num || config.abs_page)) {
printf("The operation works with blocks, not pages\n");
return false;
}
if (!config.info && !config.num_actions) {
printf("Nothing to do\n");
return false;
}
if (config.action == Actions::kSave && !config.file) {
printf("Save requires a file\n");\
return false;
}
if (config.count &&
(config.action != Actions::kReadCheck && config.action != Actions::kSave &&
config.action != Actions::kErase)) {
printf("Count not supported for this operation\n");
return false;
}
return true;
}
bool ValidateOptionsWithNand(const NandBroker& nand, const Config& config) {
if (config.action == Actions::kBbt) {
return true;
}
if (config.page_num >= nand.Info().pages_per_block) {
printf("Page not within a block:\n");
return false;
}
if (config.block_num >= nand.Info().num_blocks) {
printf("Block not within device:\n");
return false;
}
if (config.abs_page >= nand.Info().num_blocks * nand.Info().pages_per_block) {
printf("Page not within device:\n");
return false;
}
if (config.action == Actions::kErase &&
nand.Info().nand_class == fuchsia_hardware_nand_Class_PARTMAP && config.block_num < 24) {
printf("Erasing the restricted area is not a good idea, sorry\n");
return false;
}
return true;
}
bool ExecuteAction(const NandBroker& nand, const Config& config) {
if (!config.num_actions) {
return true;
}
switch (config.action) {
case Actions::kBbt :
return FindBadBlocks(nand);
case Actions::kRead : {
uint32_t abs_page = config.abs_page ? config.abs_page :
config.block_num * nand.Info().pages_per_block + config.page_num;
printf("To read page %d\n", abs_page);
return nand.DumpPage(abs_page);
}
case Actions::kErase : {
// Erase a single block by default.
uint32_t count = config.count ? config.count : 1;
if (!config.skip_prompt) {
printf("About to erase %d block(s) starting at block %d. Press y to confirm\n",
count, config.block_num);
if (getchar() != 'y') {
return false;
}
}
return Erase(nand, config.block_num, count);
}
case Actions::kReadCheck :
printf("Checking blocks...\n");
return ReadCheck(nand, config.block_num, config.count);
case Actions::kSave :
printf("Saving blocks...\n");
return Save(nand, config.block_num, config.count, config.file);
case Actions::kWear :
return WearCounts(nand);
}
return false;
}
} // namespace
int main(int argc, char** argv) {
Config config = {};
if (!GetOptions(argc, argv, &config)) {
printf("%s\n", kUsageMessage);
return -1;
}
if (!ValidateOptions(config)) {
return -1;
}
NandBroker nand(config.path);
if (!nand.Initialize()) {
printf("Unable to open the nand device\n");
return -1;
}
if (!ValidateOptionsWithNand(nand, config)) {
nand.ShowInfo();
return -1;
}
if (!config.ignore_ftl) {
nand.SetFtl(FtlInfo::Factory(&nand));
}
if (config.info) {
nand.ShowInfo();
if (!nand.ReadPages(0, 1)) {
return -1;
}
DumpPage0(nand.data());
if (nand.ftl()) {
nand.ftl()->DumpInfo();
}
}
return ExecuteAction(nand, config) ? 0 : -1;
}