blob: 1b14799da6178c3aa02d43075cb45849cafce476 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc.
*
* 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
*/
#define PROGRAM_NAME "ubiscrubber"
#include <stdio.h>
#include <getopt.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <libubi.h>
#include "common.h"
#include "ubiutils-common.h"
#define CYCLE_DURATION_MIN (60*60*24*1)
#define CYCLE_DURATION_MAX (60*60*24*7)
#define MAX_EC_END_OF_LIFE 10000
/* The variables below are set by command line arguments */
struct args {
int cycle_duration;
unsigned int verbose:1;
const char *node;
};
static struct args args = {
.cycle_duration = -1,
.verbose = 0,
.node = NULL,
};
static const char doc[] = PROGRAM_NAME " version " VERSION
" - a tool to scrub UBI volumes.";
static const char optionsstr[] =
"-d, --cycle-duration=<seconds> take this much time to read the volume\n"
"-v, --verbose be verbose\n"
"-h, --help print help message\n"
"-V, --version print program version";
static const char usage[] =
"Usage 1: " PROGRAM_NAME " <UBI volume node file name>\n"
"Usage 2: " PROGRAM_NAME "[-h] [-V] [--help] [--version]\n"
"\n"
"Example 1: " PROGRAM_NAME " /dev/ubi2_0 - scrub UBI volume /dev/ubi2_0\n";
static const struct option long_options[] = {
{ .name = "cycle-duration",
.has_arg = 1, .flag = NULL, .val = 'd' },
{ .name = "verbose", .has_arg = 0, .flag = NULL, .val = 'v' },
{ .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[])
{
int key, error = 0;
while ( (key = getopt_long(argc, argv, "d:e:hVv", long_options, NULL)) != -1) {
switch (key) {
case 'd':
args.cycle_duration = simple_strtol(optarg, &error);
if (error)
return errmsg("bad cycle duration: " "\"%s\"", optarg);
break;
case 'h':
printf("%s\n\n", doc);
printf("%s\n\n", usage);
printf("%s\n", optionsstr);
exit(EXIT_SUCCESS);
case 'v':
args.verbose = 1;
break;
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)
return errmsg("No UBI volume specified (use -h for help)");
args.node = argv[optind];
return 0;
}
struct scrubber_state {
struct ubi_vol_info vol_info;
struct ubi_dev_info dev_info;
char *pagebuf;
int fd;
/* pages = total size of volume in bytes / min_io_size */
long long pages;
long long start_page;
long long max_ec;
};
static struct scrubber_state s;
#define PRINT_DURATION_FORMAT_STRING "%dd %dh %dm %ds"
#define PRINT_DURATION_ARGS(d) (d) / (24 * 60 * 60), (d) / (60 * 60) % (24), (d) / (60) % (60), (d) % (60)
static int scrubbing_cycle_duration(int max_ec) {
int cd;
if (args.cycle_duration != -1) {
bareverbose(args.verbose, "Cycle duration (cmdline option): "
PRINT_DURATION_FORMAT_STRING "\n",
PRINT_DURATION_ARGS(args.cycle_duration));
return args.cycle_duration;
}
max_ec = MAX(max_ec, 0);
max_ec = MIN(max_ec, MAX_EC_END_OF_LIFE);
cd = (double)CYCLE_DURATION_MAX -
(double)max_ec / (double)MAX_EC_END_OF_LIFE *
((double)CYCLE_DURATION_MAX - (double)CYCLE_DURATION_MIN);
bareverbose(args.verbose, "Cycle duration: "
PRINT_DURATION_FORMAT_STRING "\n", PRINT_DURATION_ARGS(cd));
return cd;
}
static int get_info(libubi_t libubi, const char *node, struct ubi_vol_info *vol_info, struct ubi_dev_info *dev_info) {
int err;
err = ubi_get_vol_info(libubi, node, vol_info);
if (err) {
return sys_errmsg("cannot get information about UBI volume \"%s\"", node);
}
err = ubi_get_dev_info1(libubi, vol_info->dev_num, dev_info);
if (err) {
return sys_errmsg("cannot get information about UBI device %d", vol_info->dev_num);
}
return 0;
}
static int scrub_page(int page) {
ssize_t rc;
bareverbose(args.verbose, "scrubbing page %d\n", page);
lseek(s.fd, page * s.dev_info.min_io_size, SEEK_SET);
rc = read(s.fd, s.pagebuf, s.dev_info.min_io_size);
if (rc == -1) {
return sys_errmsg("read failed");
}
return 0;
}
static int scrub(void) {
long long cur_page;
int err;
int cycle_duration;
double duration;
struct timespec sleep_duration;
/* Determine duration of one cycle. */
cycle_duration = scrubbing_cycle_duration(s.max_ec);
duration = (double)cycle_duration/(double)s.pages;
sleep_duration.tv_sec = duration;
sleep_duration.tv_nsec = (duration - sleep_duration.tv_sec) * 1000000000;
/* Account for inaccurate floating point arithmetic. */
if (sleep_duration.tv_nsec > 999999999)
sleep_duration.tv_nsec = 999999999;
if (sleep_duration.tv_nsec < 0)
sleep_duration.tv_nsec = 0;
for(cur_page=0; cur_page<s.pages; cur_page++) {
while(1) {
if (nanosleep(&sleep_duration, NULL)) {
if (errno == EINTR) continue;
return sys_errmsg("nanosleep");
}
break;
}
scrub_page((s.start_page + cur_page)%s.pages);
}
bareverbose(args.verbose, "\n");
return 0;
}
int main(int argc, char * const argv[])
{
int err;
libubi_t libubi;
off_t lseek_size;
err = parse_opt(argc, argv);
if (err)
return -1;
memset(&s, 0, sizeof(s));
s.fd = -1;
libubi = libubi_open();
if (!libubi) {
if (errno == 0)
return errmsg("UBI is not present in the system");
return sys_errmsg("cannot open libubi");
}
err = get_info(libubi, args.node, &s.vol_info, &s.dev_info);
if (err)
goto out_libubi;
/* Open UBI volume */
s.fd = open(args.node, O_RDONLY);
if (s.fd == -1) {
sys_errmsg("cannot open \"%s\"", args.node);
goto out_libubi;
}
/* Determine size of UBI volume through lseek() */
lseek_size = lseek(s.fd, 0, SEEK_END);
if (lseek_size == -1) {
sys_errmsg("cannot lseek \"%s\"", args.node);
goto out_libubi;
}
s.max_ec = s.dev_info.max_ec;
bareverbose(args.verbose, "Logical eraseblock size: ");
if (args.verbose) {
ubiutils_print_bytes(s.dev_info.leb_size, 1);
}
bareverbose(args.verbose, "\n");
bareverbose(args.verbose, "Volume size: %d LEBs (", s.vol_info.rsvd_lebs);
if (args.verbose) {
ubiutils_print_bytes(s.vol_info.rsvd_bytes, 0);
}
bareverbose(args.verbose, ")\n");
bareverbose(args.verbose, "Minimum i/o unit size (i.e. page size): %d %s\n",
s.dev_info.min_io_size, s.dev_info.min_io_size > 1 ? "bytes" : "byte");
bareverbose(args.verbose, "Current max. erase counter value: %lld\n", s.dev_info.max_ec);
bareverbose(args.verbose, "Estimated max. erase counter value at EOL: %lld\n", (long long) MAX_EC_END_OF_LIFE);
/* Ensure size determined through lseek matches size announced in sysfs */
if (lseek_size != s.vol_info.rsvd_bytes) {
errmsg("Size reported by lseek(=%lld) is different from size reported by libubi", (long long) lseek_size);
goto out_libubi;
}
/* Ensure size in bytes is multiple of page size. */
if (s.vol_info.rsvd_bytes % s.dev_info.min_io_size) {
errmsg("Reserved bytes is not a multiple of min io size");
goto out_libubi;
}
/* Calculate number of pages. */
s.pages = s.vol_info.rsvd_bytes / s.dev_info.min_io_size;
/* Choose a random page where we start scrubbing at. */
ubiutils_srand();
s.start_page = (rand() * (long long) RAND_MAX + rand()) % s.pages;
/* Allocate buffer to read in flash pages */
s.pagebuf = malloc(s.dev_info.min_io_size);
if (!s.pagebuf) {
sys_errmsg("out of memory");
goto out_libubi;
}
bareverbose(args.verbose, "Total number of pages: %lld\n", s.pages);
bareverbose(args.verbose, "Scrubbing starts at page: %lld\n", s.start_page);
bareverbose(args.verbose, "Cycle duration for brand new devices: " PRINT_DURATION_FORMAT_STRING "\n", PRINT_DURATION_ARGS(CYCLE_DURATION_MAX));
bareverbose(args.verbose, "Cycle duration for devices at EOL: " PRINT_DURATION_FORMAT_STRING "\n", PRINT_DURATION_ARGS(CYCLE_DURATION_MIN));
err = scrub();
if (err)
goto out_libubi;
if (s.fd != -1) close(s.fd);
if (s.pagebuf) free(s.pagebuf);
libubi_close(libubi);
return 0;
out_libubi:
if (s.fd != -1) close(s.fd);
if (s.pagebuf) free(s.pagebuf);
libubi_close(libubi);
return -1;
}