From bc56d5755c2b1e11a8b1589ef9275710fb2060a7 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Mon, 22 Aug 2016 16:10:16 +0200 Subject: mtd-utils: Add flash speed test utility Basically a user space port of the mtd speed test kernel module. In addition to the block offset and count module parameters, the utility supports a block stride and can restore the block contents after test. Furthermore, a flag can be used to disable destructive tests (i.e. only perform read speed tests). Signed-off-by: David Oberhollenzer Signed-off-by: Richard Weinberger --- .gitignore | 1 + tests/mtd-tests/Makemodule.am | 6 +- tests/mtd-tests/flash_speed.c | 475 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 tests/mtd-tests/flash_speed.c diff --git a/.gitignore b/.gitignore index a37cbdc..5f3d4dc 100644 --- a/.gitignore +++ b/.gitignore @@ -80,6 +80,7 @@ test_1 test_2 flash_torture flash_stress +flash_speed ubiattach ubiblock ubicrc32 diff --git a/tests/mtd-tests/Makemodule.am b/tests/mtd-tests/Makemodule.am index 541bc4b..ac40697 100644 --- a/tests/mtd-tests/Makemodule.am +++ b/tests/mtd-tests/Makemodule.am @@ -6,8 +6,12 @@ flash_stress_SOURCES = tests/mtd-tests/flash_stress.c flash_stress_LDADD = libmtd.a flash_stress_CPPFLAGS = $(AM_CPPFLAGS) +flash_speed_SOURCES = tests/mtd-tests/flash_speed.c +flash_speed_LDADD = libmtd.a +flash_speed_CPPFLAGS = $(AM_CPPFLAGS) + MTDTEST_BINS = \ - flash_torture flash_stress + flash_torture flash_stress flash_speed if INSTALL_TESTS pkglibexec_PROGRAMS += $(MTDTEST_BINS) diff --git a/tests/mtd-tests/flash_speed.c b/tests/mtd-tests/flash_speed.c new file mode 100644 index 0000000..d5cedba --- /dev/null +++ b/tests/mtd-tests/flash_speed.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2007 Nokia Corporation + * Copyright (C) 2015 sigma star gmbh + * + * 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; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Test read and write speed of a MTD device. + * + * Author: David Oberhollenzer + * + * Based on linux flash_speed.c + * Author: Adrian Hunter + */ +#define DESTRUCTIVE 0x01 + +#define PROGRAM_NAME "flash_speed" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +static struct mtd_dev_info mtd; +static unsigned char *iobuf; +static unsigned char *bbt; +static const char *mtddev; +static libmtd_t mtd_desc; +static int fd; + +static int peb=-1, count=-1, skip=-1, flags=0; +static struct timespec start, finish; +static int pgsize, pgcnt; +static int goodebcnt; + +static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "destructive", no_argument, NULL, 'd' }, + { "peb", required_argument, NULL, 'b' }, + { "count", required_argument, NULL, 'c' }, + { "skip", required_argument, NULL, 's' }, + { NULL, 0, NULL, 0 }, +}; + +static void usage(int status) +{ + fputs( + "Usage: "PROGRAM_NAME" [OPTIONS] \n\n" + "Common options:\n" + " -h, --help Display this help output\n" + " -b, --peb Start from this physical erase block\n" + " -c, --count Number of erase blocks to use (default: all)\n" + " -s, --skip Number of blocks to skip\n" + " -d, --destructive Run destructive (erase and write speed) tests\n", + status==EXIT_SUCCESS ? stdout : stderr); + exit(status); +} + +static long read_num(int opt, const char *arg) +{ + char *end; + long num; + + num = strtol(arg, &end, 0); + + if (!end || *end != '\0') { + fprintf(stderr, "-%c: expected integer argument\n", opt); + exit(EXIT_FAILURE); + } + return num; +} + +static void process_options(int argc, char **argv) +{ + int c; + + while (1) { + c = getopt_long(argc, argv, "hb:c:s:d", options, NULL); + if (c == -1) + break; + + switch (c) { + case 'h': + usage(EXIT_SUCCESS); + break; + case 'b': + if (peb >= 0) + goto failmulti; + peb = read_num(c, optarg); + if (peb < 0) + goto failarg; + break; + case 'c': + if (count > 0) + goto failmulti; + count = read_num(c, optarg); + if (count <= 0) + goto failarg; + break; + case 's': + if (skip >= 0) + goto failmulti; + skip = read_num(c, optarg); + if (skip < 0) + goto failarg; + break; + case 'd': + if (flags & DESTRUCTIVE) + goto failmulti; + flags |= DESTRUCTIVE; + break; + default: + exit(EXIT_FAILURE); + } + } + + if (optind < argc) + mtddev = argv[optind++]; + else + errmsg_die("No device specified!\n"); + + if (optind < argc) + usage(EXIT_FAILURE); + if (peb < 0) + peb = 0; + if (skip < 0) + skip = 0; + if (count < 0) + count = 1; + return; +failmulti: + errmsg_die("'-%c' specified more than once!\n", c); +failarg: + errmsg_die("Invalid argument for '-%c'!\n", c); +} + +static int write_eraseblock(int ebnum) +{ + int err = mtd_write(mtd_desc, &mtd, fd, ebnum, 0, + iobuf, mtd.eb_size, NULL, 0, 0); + if (err) + fprintf(stderr, "Error writing block %d!\n", ebnum); + return err; +} + +static int read_eraseblock(int ebnum) +{ + int err = mtd_read(&mtd, fd, ebnum, 0, iobuf, mtd.eb_size); + if (err) + fprintf(stderr, "Error writing block %d!\n", ebnum); + return err; +} + +static int write_eraseblock_by_page(int ebnum) +{ + void *buf = iobuf; + int i, err = 0; + + for (i = 0; i < pgcnt; ++i) { + err = mtd_write(mtd_desc, &mtd, fd, ebnum, i * pgsize, + buf, pgsize, NULL, 0, 0); + if (err) { + fprintf(stderr, "Error writing block %d, page %d!\n", + ebnum, i); + break; + } + buf += pgsize; + } + + return err; +} + +static int write_eraseblock_by_2pages(int ebnum) +{ + int i, n = pgcnt / 2, err = 0; + size_t sz = pgsize * 2; + void *buf = iobuf; + + for (i = 0; i < n; ++i) { + err = mtd_write(mtd_desc, &mtd, fd, ebnum, i * sz, + buf, sz, NULL, 0, 0); + if (err) { + fprintf(stderr, "Error writing block %d, page %d + %d!\n", + ebnum, i*2, i*2+1); + return err; + } + buf += sz; + } + if (pgcnt % 2) { + err = mtd_write(mtd_desc, &mtd, fd, ebnum, i * sz, + buf, pgsize, NULL, 0, 0); + if (err) { + fprintf(stderr, "Error reading block %d, page %d!\n", + ebnum, i*2); + } + } + return err; +} + +static int read_eraseblock_by_page(int ebnum) +{ + void *buf = iobuf; + int i, err = 0; + + for (i = 0; i < pgcnt; ++i) { + err = mtd_read(&mtd, fd, ebnum, i * pgsize, iobuf, pgsize); + if (err) { + fprintf(stderr, "Error reading block %d, page %d!\n", + ebnum, i); + break; + } + buf += pgsize; + } + + return err; +} + +static int read_eraseblock_by_2pages(int ebnum) +{ + int i, n = pgcnt / 2, err = 0; + size_t sz = pgsize * 2; + void *buf = iobuf; + + for (i = 0; i < n; ++i) { + err = mtd_read(&mtd, fd, ebnum, i * sz, iobuf, sz); + if (err) { + fprintf(stderr, "Error reading block %d, page %d + %d!\n", + ebnum, i*2, i*2+1); + return err; + } + buf += sz; + } + if (pgcnt % 2) { + err = mtd_read(&mtd, fd, ebnum, i * sz, iobuf, pgsize); + if (err) { + fprintf(stderr, "Error reading block %d, page %d!\n", + ebnum, i*2); + } + } + + return err; +} + +static void start_timing(void) +{ + clock_gettime(CLOCK_MONOTONIC_RAW, &start); +} + +static void stop_timing(void) +{ + clock_gettime(CLOCK_MONOTONIC_RAW, &finish); +} + +static long calc_speed(void) +{ + long ms; + + ms = (finish.tv_sec - start.tv_sec) * 1000L; + ms += (finish.tv_nsec - start.tv_nsec) / 1000000L; + + if (ms <= 0) + return 0; + + return ((long)goodebcnt * (mtd.eb_size / 1024L) * 1000L) / ms; +} + +static void scan_for_bad_eraseblocks(unsigned int eb, int ebcnt, int ebskip) +{ + int i, bad = 0; + + puts("scanning for bad eraseblocks"); + + for (i = 0; i < ebcnt; ++i) { + bbt[i] = mtd_is_bad(&mtd, fd, eb + i*(ebskip+1)) ? 1 : 0; + if (bbt[i]) + bad += 1; + } + + printf("scanned %d eraseblocks, %d are bad\n", ebcnt, bad); +} + +static int erase_good_eraseblocks(unsigned int eb, int ebcnt, int ebskip) +{ + int err = 0, block; + unsigned int i; + + for (i = 0; i < ebcnt; ++i) { + if (bbt[i]) + continue; + block = eb + i*(ebskip+1); + err = mtd_erase(mtd_desc, &mtd, fd, block); + if (err) + fprintf(stderr, "Error erasing block %d!\n", block); + } + + return err; +} + +#define TIME_OP_PER_PEB( op )\ + start_timing();\ + for (i = 0; i < count; ++i) {\ + if (bbt[i])\ + continue;\ + err = op(peb + i*(skip+1));\ + if (err)\ + goto out;\ + }\ + stop_timing();\ + speed = calc_speed() + +int main(int argc, char **argv) +{ + int err, i, blocks, j, k, status = EXIT_FAILURE; + long speed; + + process_options(argc, argv); + + mtd_desc = libmtd_open(); + if (!mtd_desc) + return errmsg("can't initialize libmtd"); + + if (mtd_get_dev_info(mtd_desc, mtddev, &mtd) < 0) + return errmsg("mtd_get_dev_info failed"); + + if (mtd.subpage_size == 1) { + puts("not NAND flash, assume page size is 512 bytes."); + pgsize = 512; + } else { + pgsize = mtd.subpage_size; + } + + pgcnt = mtd.eb_size / pgsize; + + if (count < 0) + count = mtd.eb_size; + + if (peb >= mtd.eb_cnt) + return errmsg("Physical erase block %d is out of range!\n", peb); + + if ((peb + (count - 1)*(skip + 1)) >= mtd.eb_cnt) { + return errmsg("Given block range exceeds block count of %d!\n", + mtd.eb_cnt); + } + + iobuf = xmalloc(mtd.eb_size); + bbt = xzalloc(count); + + if ((fd = open(mtddev, O_RDWR)) == -1) { + perror(mtddev); + goto outfree; + } + + for (i = 0; i < mtd.eb_size; ++i) + iobuf[i] = rand(); + + scan_for_bad_eraseblocks(peb, count, skip); + + for (i = 0; i < count; ++i) { + if (!bbt[i]) + goodebcnt++; + } + + /* Write all eraseblocks, 1 eraseblock at a time */ + if (flags & DESTRUCTIVE) { + err = erase_good_eraseblocks(peb, count, skip); + if (err) + goto out; + + puts("testing eraseblock write speed"); + TIME_OP_PER_PEB(write_eraseblock); + printf("eraseblock write speed is %ld KiB/s\n", speed); + } + + /* Read all eraseblocks, 1 eraseblock at a time */ + puts("testing eraseblock read speed"); + TIME_OP_PER_PEB(read_eraseblock); + printf("eraseblock read speed is %ld KiB/s\n", speed); + + /* Write all eraseblocks, 1 page at a time */ + if (flags & DESTRUCTIVE) { + err = erase_good_eraseblocks(peb, count, skip); + if (err) + goto out; + + puts("testing page write speed"); + TIME_OP_PER_PEB(write_eraseblock_by_page); + printf("page write speed is %ld KiB/s\n", speed); + } + + /* Read all eraseblocks, 1 page at a time */ + puts("testing page read speed"); + TIME_OP_PER_PEB(read_eraseblock_by_page); + printf("page read speed is %ld KiB/s\n", speed); + + /* Write all eraseblocks, 2 pages at a time */ + if (flags & DESTRUCTIVE) { + err = erase_good_eraseblocks(peb, count, skip); + if (err) + goto out; + + puts("testing 2 page write speed"); + TIME_OP_PER_PEB(write_eraseblock_by_2pages); + printf("2 page write speed is %ld KiB/s\n", speed); + } + + /* Read all eraseblocks, 2 pages at a time */ + puts("testing 2 page read speed"); + TIME_OP_PER_PEB(read_eraseblock_by_2pages); + printf("2 page read speed is %ld KiB/s\n", speed); + + /* Erase all eraseblocks */ + if (flags & DESTRUCTIVE) { + puts("Testing erase speed"); + start_timing(); + err = erase_good_eraseblocks(peb, count, skip); + if (err) + goto out; + stop_timing(); + speed = calc_speed(); + printf("erase speed is %ld KiB/s\n", speed); + } + + /* Multi-block erase all eraseblocks */ + if (!skip) { + for (k = 1; k < 7; ++k) { + blocks = 1 << k; + printf("Testing %dx multi-block erase speed\n", blocks); + start_timing(); + for (i = 0; i < count; ) { + for (j = 0; j < blocks && (i + j) < count; ++j) + if (bbt[i + j]) + break; + if (j < 1) { + ++i; + continue; + } + err = mtd_erase_multi(mtd_desc, &mtd, fd, i, j); + if (err) + goto out; + i += j; + } + stop_timing(); + speed = calc_speed(); + printf("%dx multi-block erase speed is %ld KiB/s\n", + blocks, speed); + } + } + + puts("finished"); + status = EXIT_SUCCESS; +out: + close(fd); +outfree: + free(iobuf); + free(bbt); + return status; +} -- cgit v1.2.3