438 lines
11 KiB
C
438 lines
11 KiB
C
/*
|
|
* Copyright (C) 2009 Nokia Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc., 51
|
|
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/*
|
|
* An utility to get MTD information.
|
|
*
|
|
* Author: Artem Bityutskiy
|
|
*/
|
|
|
|
#define PROGRAM_NAME "mtdinfo"
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <mtd/mtd-user.h>
|
|
|
|
#include <libubigen.h>
|
|
#include <libmtd.h>
|
|
#include "common.h"
|
|
#include "ubiutils-common.h"
|
|
|
|
/* The variables below are set by command line arguments */
|
|
struct args {
|
|
unsigned int all:1;
|
|
unsigned int ubinfo:1;
|
|
unsigned int map:1;
|
|
const char *node;
|
|
};
|
|
|
|
static struct args args = {
|
|
.ubinfo = 0,
|
|
.all = 0,
|
|
.node = NULL,
|
|
};
|
|
|
|
static void display_help(void)
|
|
{
|
|
printf(
|
|
"%1$s version %2$s - a tool to print MTD information.\n"
|
|
"\n"
|
|
"Usage: %1$s <MTD node file path> [--map | -M] [--ubi-info | -u]\n"
|
|
" %1$s --all [--ubi-info | -u]\n"
|
|
" %1$s [--help | --version]\n"
|
|
"\n"
|
|
"Options:\n"
|
|
"-u, --ubi-info print what would UBI layout be if it was put\n"
|
|
" on this MTD device\n"
|
|
"-M, --map print eraseblock map\n"
|
|
"-a, --all print information about all MTD devices\n"
|
|
" Note: `--all' may give less info per device\n"
|
|
" than, e.g., `mtdinfo /dev/mtdX'\n"
|
|
"-h, --help print help message\n"
|
|
"-V, --version print program version\n"
|
|
"\n"
|
|
"Examples:\n"
|
|
" %1$s /dev/mtd0 print information MTD device /dev/mtd0\n"
|
|
" %1$s /dev/mtd0 -u print information MTD device /dev/mtd0\n"
|
|
" %4$*3$s and include UBI layout information\n"
|
|
" %1$s -a print information about all MTD devices\n",
|
|
PROGRAM_NAME, VERSION, (int)strlen(PROGRAM_NAME) + 3, "");
|
|
}
|
|
|
|
static const struct option long_options[] = {
|
|
{ .name = "ubi-info", .has_arg = 0, .flag = NULL, .val = 'u' },
|
|
{ .name = "map", .has_arg = 0, .flag = NULL, .val = 'M' },
|
|
{ .name = "all", .has_arg = 0, .flag = NULL, .val = 'a' },
|
|
{ .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' },
|
|
{ .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' },
|
|
{ NULL, 0, NULL, 0},
|
|
};
|
|
|
|
static int parse_opt(int argc, char * const argv[])
|
|
{
|
|
while (1) {
|
|
int key;
|
|
|
|
key = getopt_long(argc, argv, "auMhV", long_options, NULL);
|
|
if (key == -1)
|
|
break;
|
|
|
|
switch (key) {
|
|
case 'a':
|
|
args.all = 1;
|
|
break;
|
|
|
|
case 'u':
|
|
args.ubinfo = 1;
|
|
break;
|
|
|
|
case 'M':
|
|
args.map = 1;
|
|
break;
|
|
|
|
case 'h':
|
|
display_help();
|
|
exit(EXIT_SUCCESS);
|
|
|
|
case 'V':
|
|
common_print_version();
|
|
exit(EXIT_SUCCESS);
|
|
|
|
case ':':
|
|
return errmsg("parameter is missing");
|
|
|
|
default:
|
|
fprintf(stderr, "Use -h for help\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (optind == argc - 1)
|
|
args.node = argv[optind];
|
|
else if (optind < argc)
|
|
return errmsg("more then one MTD device specified (use -h for help)");
|
|
|
|
if (args.all && args.node)
|
|
args.node = NULL;
|
|
|
|
if (args.map && !args.node)
|
|
return errmsg("-M requires MTD device node name");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int translate_dev(libmtd_t libmtd, const char *node)
|
|
{
|
|
int err;
|
|
struct mtd_dev_info mtd;
|
|
|
|
err = mtd_get_dev_info(libmtd, node, &mtd);
|
|
if (err) {
|
|
if (errno == ENODEV)
|
|
return errmsg("\"%s\" does not correspond to any "
|
|
"existing MTD device", node);
|
|
return sys_errmsg("cannot get information about MTD "
|
|
"device \"%s\"", node);
|
|
}
|
|
|
|
return mtd.mtd_num;
|
|
}
|
|
|
|
static void print_ubi_info(const struct mtd_info *mtd_info,
|
|
const struct mtd_dev_info *mtd)
|
|
{
|
|
struct ubigen_info ui;
|
|
|
|
if (!mtd_info->sysfs_supported) {
|
|
errmsg("cannot provide UBI info, becasue sub-page size is "
|
|
"not known");
|
|
return;
|
|
}
|
|
|
|
ubigen_info_init(&ui, mtd->eb_size, mtd->min_io_size, mtd->subpage_size,
|
|
0, 1, 0, 0);
|
|
printf("Default UBI VID header offset: %d\n", ui.vid_hdr_offs);
|
|
printf("Default UBI data offset: %d\n", ui.data_offs);
|
|
printf("Default UBI LEB size: ");
|
|
ubiutils_print_bytes(ui.leb_size, 0);
|
|
printf("\n");
|
|
printf("Maximum UBI volumes count: %d\n", ui.max_volumes);
|
|
}
|
|
|
|
static void print_region_map(const struct mtd_dev_info *mtd, int fd,
|
|
const region_info_t *reginfo)
|
|
{
|
|
unsigned long start;
|
|
int i, width;
|
|
int ret_locked, errno_locked, ret_bad, errno_bad;
|
|
|
|
printf("Eraseblock map:\n");
|
|
|
|
/* Figure out the number of spaces to pad w/out libm */
|
|
for (i = 1, width = 0; i < reginfo->numblocks; i *= 10, ++width)
|
|
continue;
|
|
|
|
/* If we don't have a fd to query, just show the bare map */
|
|
if (fd == -1) {
|
|
ret_locked = ret_bad = -1;
|
|
errno_locked = errno_bad = ENODEV;
|
|
} else
|
|
ret_locked = ret_bad = errno_locked = errno_bad = 0;
|
|
|
|
for (i = 0; i < reginfo->numblocks; ++i) {
|
|
start = reginfo->offset + i * reginfo->erasesize;
|
|
printf(" %*i: %08lx ", width, i, start);
|
|
|
|
if (ret_locked != -1) {
|
|
ret_locked = mtd_is_locked(mtd, fd, i);
|
|
if (ret_locked == 1)
|
|
printf("RO ");
|
|
else
|
|
errno_locked = errno;
|
|
}
|
|
if (ret_locked != 1)
|
|
printf(" ");
|
|
|
|
if (ret_bad != -1) {
|
|
ret_bad = mtd_is_bad(mtd, fd, i);
|
|
if (ret_bad == 1)
|
|
printf("BAD ");
|
|
else
|
|
errno_bad = errno;
|
|
}
|
|
if (ret_bad != 1)
|
|
printf(" ");
|
|
|
|
if (((i + 1) % 4) == 0)
|
|
printf("\n");
|
|
}
|
|
if (i % 4)
|
|
printf("\n");
|
|
|
|
if (ret_locked == -1 && errno_locked != EOPNOTSUPP) {
|
|
errno = errno_locked;
|
|
sys_errmsg("could not read locked block info");
|
|
}
|
|
|
|
if (mtd->bb_allowed && ret_bad == -1 && errno_bad != EOPNOTSUPP) {
|
|
errno = errno_bad;
|
|
sys_errmsg("could not read bad block info");
|
|
}
|
|
}
|
|
|
|
static void print_region_info(const struct mtd_dev_info *mtd)
|
|
{
|
|
region_info_t reginfo;
|
|
int r, fd;
|
|
|
|
/*
|
|
* If we don't have any region info, just return
|
|
*
|
|
* FIXME: We can't get region_info (via ioctl) without having the MTD
|
|
* node path. This is a problem for `mtdinfo -a', for example,
|
|
* since it doesn't provide any filepath information.
|
|
*/
|
|
if (!args.node || (!args.map && mtd->region_cnt == 0))
|
|
return;
|
|
|
|
/* First open the device so we can query it */
|
|
fd = open(args.node, O_RDONLY | O_CLOEXEC);
|
|
if (fd == -1) {
|
|
sys_errmsg("couldn't open MTD dev: %s", args.node);
|
|
if (mtd->region_cnt)
|
|
return;
|
|
}
|
|
|
|
/* Walk all the regions and show the map for them */
|
|
if (mtd->region_cnt) {
|
|
for (r = 0; r < mtd->region_cnt; ++r) {
|
|
printf("Eraseblock region %i: ", r);
|
|
if (mtd_regioninfo(fd, r, ®info) == 0) {
|
|
printf(" offset: %#x size: %#x numblocks: %#x\n",
|
|
reginfo.offset, reginfo.erasesize,
|
|
reginfo.numblocks);
|
|
if (args.map)
|
|
print_region_map(mtd, fd, ®info);
|
|
} else
|
|
printf(" info is unavailable\n");
|
|
}
|
|
} else {
|
|
reginfo.offset = 0;
|
|
reginfo.erasesize = mtd->eb_size;
|
|
reginfo.numblocks = mtd->eb_cnt;
|
|
reginfo.regionindex = 0;
|
|
print_region_map(mtd, fd, ®info);
|
|
}
|
|
|
|
if (fd != -1)
|
|
close(fd);
|
|
}
|
|
|
|
static int print_dev_info(libmtd_t libmtd, const struct mtd_info *mtd_info, int mtdn)
|
|
{
|
|
int err;
|
|
struct mtd_dev_info mtd;
|
|
|
|
err = mtd_get_dev_info1(libmtd, mtdn, &mtd);
|
|
if (err) {
|
|
if (errno == ENODEV)
|
|
return errmsg("mtd%d does not correspond to any "
|
|
"existing MTD device", mtdn);
|
|
return sys_errmsg("cannot get information about MTD device %d",
|
|
mtdn);
|
|
}
|
|
|
|
printf("mtd%d\n", mtd.mtd_num);
|
|
printf("Name: %s\n", mtd.name);
|
|
printf("Type: %s\n", mtd.type_str);
|
|
printf("Eraseblock size: ");
|
|
ubiutils_print_bytes(mtd.eb_size, 0);
|
|
printf("\n");
|
|
printf("Amount of eraseblocks: %d (", mtd.eb_cnt);
|
|
ubiutils_print_bytes(mtd.size, 0);
|
|
printf(")\n");
|
|
printf("Minimum input/output unit size: %d %s\n",
|
|
mtd.min_io_size, mtd.min_io_size > 1 ? "bytes" : "byte");
|
|
if (mtd_info->sysfs_supported)
|
|
printf("Sub-page size: %d %s\n",
|
|
mtd.subpage_size,
|
|
mtd.subpage_size > 1 ? "bytes" : "byte");
|
|
else if (mtd.type == MTD_NANDFLASH || mtd.type == MTD_MLCNANDFLASH)
|
|
printf("Sub-page size: unknown\n");
|
|
|
|
if (mtd.oob_size > 0)
|
|
printf("OOB size: %d bytes\n",
|
|
mtd.oob_size);
|
|
if (mtd.region_cnt > 0)
|
|
printf("Additional erase regions: %d\n", mtd.oob_size);
|
|
if (mtd_info->sysfs_supported)
|
|
printf("Character device major/minor: %d:%d\n",
|
|
mtd.major, mtd.minor);
|
|
printf("Bad blocks are allowed: %s\n",
|
|
mtd.bb_allowed ? "true" : "false");
|
|
printf("Device is writable: %s\n",
|
|
mtd.writable ? "true" : "false");
|
|
|
|
if (args.ubinfo)
|
|
print_ubi_info(mtd_info, &mtd);
|
|
|
|
print_region_info(&mtd);
|
|
|
|
printf("\n");
|
|
return 0;
|
|
}
|
|
|
|
static int print_general_info(libmtd_t libmtd, const struct mtd_info *mtd_info,
|
|
int all)
|
|
{
|
|
int i, err, first = 1;
|
|
struct mtd_dev_info mtd;
|
|
|
|
printf("Count of MTD devices: %d\n", mtd_info->mtd_dev_cnt);
|
|
if (mtd_info->mtd_dev_cnt == 0)
|
|
return 0;
|
|
|
|
for (i = mtd_info->lowest_mtd_num;
|
|
i <= mtd_info->highest_mtd_num; i++) {
|
|
err = mtd_get_dev_info1(libmtd, i, &mtd);
|
|
if (err == -1) {
|
|
if (errno == ENODEV)
|
|
continue;
|
|
return sys_errmsg("libmtd failed to get MTD device %d "
|
|
"information", i);
|
|
}
|
|
|
|
if (!first)
|
|
printf(", mtd%d", i);
|
|
else {
|
|
printf("Present MTD devices: mtd%d", i);
|
|
first = 0;
|
|
}
|
|
}
|
|
printf("\n");
|
|
printf("Sysfs interface supported: %s\n",
|
|
mtd_info->sysfs_supported ? "yes" : "no");
|
|
|
|
if (!all)
|
|
return 0;
|
|
|
|
printf("\n");
|
|
|
|
for (i = mtd_info->lowest_mtd_num;
|
|
i <= mtd_info->highest_mtd_num; i++) {
|
|
if (!mtd_dev_present(libmtd, i))
|
|
continue;
|
|
err = print_dev_info(libmtd, mtd_info, i);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char * const argv[])
|
|
{
|
|
int err;
|
|
libmtd_t libmtd;
|
|
struct mtd_info mtd_info;
|
|
|
|
err = parse_opt(argc, argv);
|
|
if (err)
|
|
return -1;
|
|
|
|
libmtd = libmtd_open();
|
|
if (libmtd == NULL) {
|
|
if (errno == 0)
|
|
return errmsg("MTD is not present in the system");
|
|
return sys_errmsg("cannot open libmtd");
|
|
}
|
|
|
|
err = mtd_get_info(libmtd, &mtd_info);
|
|
if (err) {
|
|
if (errno == ENODEV)
|
|
return errmsg("MTD is not present");
|
|
return sys_errmsg("cannot get MTD information");
|
|
}
|
|
|
|
if (!args.all && args.node) {
|
|
int mtdn;
|
|
|
|
/*
|
|
* A character device was specified, translate this to MTD
|
|
* device number.
|
|
*/
|
|
mtdn = translate_dev(libmtd, args.node);
|
|
if (mtdn < 0)
|
|
goto out_libmtd;
|
|
err = print_dev_info(libmtd, &mtd_info, mtdn);
|
|
} else
|
|
err = print_general_info(libmtd, &mtd_info, args.all);
|
|
if (err)
|
|
goto out_libmtd;
|
|
|
|
libmtd_close(libmtd);
|
|
return 0;
|
|
|
|
out_libmtd:
|
|
libmtd_close(libmtd);
|
|
return -1;
|
|
}
|