Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,18 @@
obj-$(CONFIG_MTD_TESTS) += mtd_oobtest.o
obj-$(CONFIG_MTD_TESTS) += mtd_pagetest.o
obj-$(CONFIG_MTD_TESTS) += mtd_readtest.o
obj-$(CONFIG_MTD_TESTS) += mtd_speedtest.o
obj-$(CONFIG_MTD_TESTS) += mtd_stresstest.o
obj-$(CONFIG_MTD_TESTS) += mtd_subpagetest.o
obj-$(CONFIG_MTD_TESTS) += mtd_torturetest.o
obj-$(CONFIG_MTD_TESTS) += mtd_nandecctest.o
obj-$(CONFIG_MTD_TESTS) += mtd_nandbiterrs.o
mtd_oobtest-objs := oobtest.o mtd_test.o
mtd_pagetest-objs := pagetest.o mtd_test.o
mtd_readtest-objs := readtest.o mtd_test.o
mtd_speedtest-objs := speedtest.o mtd_test.o
mtd_stresstest-objs := stresstest.o mtd_test.o
mtd_subpagetest-objs := subpagetest.o mtd_test.o
mtd_torturetest-objs := torturetest.o mtd_test.o
mtd_nandbiterrs-objs := nandbiterrs.o mtd_test.o

View file

@ -0,0 +1,316 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/random.h>
#include <linux/string.h>
#include <linux/bitops.h>
#include <linux/slab.h>
#include <linux/mtd/nand_ecc.h>
/*
* Test the implementation for software ECC
*
* No actual MTD device is needed, So we don't need to warry about losing
* important data by human error.
*
* This covers possible patterns of corruption which can be reliably corrected
* or detected.
*/
#if IS_ENABLED(CONFIG_MTD_NAND)
struct nand_ecc_test {
const char *name;
void (*prepare)(void *, void *, void *, void *, const size_t);
int (*verify)(void *, void *, void *, const size_t);
};
/*
* The reason for this __change_bit_le() instead of __change_bit() is to inject
* bit error properly within the region which is not a multiple of
* sizeof(unsigned long) on big-endian systems
*/
#ifdef __LITTLE_ENDIAN
#define __change_bit_le(nr, addr) __change_bit(nr, addr)
#elif defined(__BIG_ENDIAN)
#define __change_bit_le(nr, addr) \
__change_bit((nr) ^ ((BITS_PER_LONG - 1) & ~0x7), addr)
#else
#error "Unknown byte order"
#endif
static void single_bit_error_data(void *error_data, void *correct_data,
size_t size)
{
unsigned int offset = prandom_u32() % (size * BITS_PER_BYTE);
memcpy(error_data, correct_data, size);
__change_bit_le(offset, error_data);
}
static void double_bit_error_data(void *error_data, void *correct_data,
size_t size)
{
unsigned int offset[2];
offset[0] = prandom_u32() % (size * BITS_PER_BYTE);
do {
offset[1] = prandom_u32() % (size * BITS_PER_BYTE);
} while (offset[0] == offset[1]);
memcpy(error_data, correct_data, size);
__change_bit_le(offset[0], error_data);
__change_bit_le(offset[1], error_data);
}
static unsigned int random_ecc_bit(size_t size)
{
unsigned int offset = prandom_u32() % (3 * BITS_PER_BYTE);
if (size == 256) {
/*
* Don't inject a bit error into the insignificant bits (16th
* and 17th bit) in ECC code for 256 byte data block
*/
while (offset == 16 || offset == 17)
offset = prandom_u32() % (3 * BITS_PER_BYTE);
}
return offset;
}
static void single_bit_error_ecc(void *error_ecc, void *correct_ecc,
size_t size)
{
unsigned int offset = random_ecc_bit(size);
memcpy(error_ecc, correct_ecc, 3);
__change_bit_le(offset, error_ecc);
}
static void double_bit_error_ecc(void *error_ecc, void *correct_ecc,
size_t size)
{
unsigned int offset[2];
offset[0] = random_ecc_bit(size);
do {
offset[1] = random_ecc_bit(size);
} while (offset[0] == offset[1]);
memcpy(error_ecc, correct_ecc, 3);
__change_bit_le(offset[0], error_ecc);
__change_bit_le(offset[1], error_ecc);
}
static void no_bit_error(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
memcpy(error_data, correct_data, size);
memcpy(error_ecc, correct_ecc, 3);
}
static int no_bit_error_verify(void *error_data, void *error_ecc,
void *correct_data, const size_t size)
{
unsigned char calc_ecc[3];
int ret;
__nand_calculate_ecc(error_data, size, calc_ecc);
ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size);
if (ret == 0 && !memcmp(correct_data, error_data, size))
return 0;
return -EINVAL;
}
static void single_bit_error_in_data(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
single_bit_error_data(error_data, correct_data, size);
memcpy(error_ecc, correct_ecc, 3);
}
static void single_bit_error_in_ecc(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
memcpy(error_data, correct_data, size);
single_bit_error_ecc(error_ecc, correct_ecc, size);
}
static int single_bit_error_correct(void *error_data, void *error_ecc,
void *correct_data, const size_t size)
{
unsigned char calc_ecc[3];
int ret;
__nand_calculate_ecc(error_data, size, calc_ecc);
ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size);
if (ret == 1 && !memcmp(correct_data, error_data, size))
return 0;
return -EINVAL;
}
static void double_bit_error_in_data(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
double_bit_error_data(error_data, correct_data, size);
memcpy(error_ecc, correct_ecc, 3);
}
static void single_bit_error_in_data_and_ecc(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
single_bit_error_data(error_data, correct_data, size);
single_bit_error_ecc(error_ecc, correct_ecc, size);
}
static void double_bit_error_in_ecc(void *error_data, void *error_ecc,
void *correct_data, void *correct_ecc, const size_t size)
{
memcpy(error_data, correct_data, size);
double_bit_error_ecc(error_ecc, correct_ecc, size);
}
static int double_bit_error_detect(void *error_data, void *error_ecc,
void *correct_data, const size_t size)
{
unsigned char calc_ecc[3];
int ret;
__nand_calculate_ecc(error_data, size, calc_ecc);
ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size);
return (ret == -1) ? 0 : -EINVAL;
}
static const struct nand_ecc_test nand_ecc_test[] = {
{
.name = "no-bit-error",
.prepare = no_bit_error,
.verify = no_bit_error_verify,
},
{
.name = "single-bit-error-in-data-correct",
.prepare = single_bit_error_in_data,
.verify = single_bit_error_correct,
},
{
.name = "single-bit-error-in-ecc-correct",
.prepare = single_bit_error_in_ecc,
.verify = single_bit_error_correct,
},
{
.name = "double-bit-error-in-data-detect",
.prepare = double_bit_error_in_data,
.verify = double_bit_error_detect,
},
{
.name = "single-bit-error-in-data-and-ecc-detect",
.prepare = single_bit_error_in_data_and_ecc,
.verify = double_bit_error_detect,
},
{
.name = "double-bit-error-in-ecc-detect",
.prepare = double_bit_error_in_ecc,
.verify = double_bit_error_detect,
},
};
static void dump_data_ecc(void *error_data, void *error_ecc, void *correct_data,
void *correct_ecc, const size_t size)
{
pr_info("hexdump of error data:\n");
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4,
error_data, size, false);
print_hex_dump(KERN_INFO, "hexdump of error ecc: ",
DUMP_PREFIX_NONE, 16, 1, error_ecc, 3, false);
pr_info("hexdump of correct data:\n");
print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4,
correct_data, size, false);
print_hex_dump(KERN_INFO, "hexdump of correct ecc: ",
DUMP_PREFIX_NONE, 16, 1, correct_ecc, 3, false);
}
static int nand_ecc_test_run(const size_t size)
{
int i;
int err = 0;
void *error_data;
void *error_ecc;
void *correct_data;
void *correct_ecc;
error_data = kmalloc(size, GFP_KERNEL);
error_ecc = kmalloc(3, GFP_KERNEL);
correct_data = kmalloc(size, GFP_KERNEL);
correct_ecc = kmalloc(3, GFP_KERNEL);
if (!error_data || !error_ecc || !correct_data || !correct_ecc) {
err = -ENOMEM;
goto error;
}
prandom_bytes(correct_data, size);
__nand_calculate_ecc(correct_data, size, correct_ecc);
for (i = 0; i < ARRAY_SIZE(nand_ecc_test); i++) {
nand_ecc_test[i].prepare(error_data, error_ecc,
correct_data, correct_ecc, size);
err = nand_ecc_test[i].verify(error_data, error_ecc,
correct_data, size);
if (err) {
pr_err("not ok - %s-%zd\n",
nand_ecc_test[i].name, size);
dump_data_ecc(error_data, error_ecc,
correct_data, correct_ecc, size);
break;
}
pr_info("ok - %s-%zd\n",
nand_ecc_test[i].name, size);
}
error:
kfree(error_data);
kfree(error_ecc);
kfree(correct_data);
kfree(correct_ecc);
return err;
}
#else
static int nand_ecc_test_run(const size_t size)
{
return 0;
}
#endif
static int __init ecc_test_init(void)
{
int err;
err = nand_ecc_test_run(256);
if (err)
return err;
return nand_ecc_test_run(512);
}
static void __exit ecc_test_exit(void)
{
}
module_init(ecc_test_init);
module_exit(ecc_test_exit);
MODULE_DESCRIPTION("NAND ECC function test module");
MODULE_AUTHOR("Akinobu Mita");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,113 @@
#define pr_fmt(fmt) "mtd_test: " fmt
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/printk.h>
#include "mtd_test.h"
int mtdtest_erase_eraseblock(struct mtd_info *mtd, unsigned int ebnum)
{
int err;
struct erase_info ei;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
memset(&ei, 0, sizeof(struct erase_info));
ei.mtd = mtd;
ei.addr = addr;
ei.len = mtd->erasesize;
err = mtd_erase(mtd, &ei);
if (err) {
pr_info("error %d while erasing EB %d\n", err, ebnum);
return err;
}
if (ei.state == MTD_ERASE_FAILED) {
pr_info("some erase error occurred at EB %d\n", ebnum);
return -EIO;
}
return 0;
}
static int is_block_bad(struct mtd_info *mtd, unsigned int ebnum)
{
int ret;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
ret = mtd_block_isbad(mtd, addr);
if (ret)
pr_info("block %d is bad\n", ebnum);
return ret;
}
int mtdtest_scan_for_bad_eraseblocks(struct mtd_info *mtd, unsigned char *bbt,
unsigned int eb, int ebcnt)
{
int i, bad = 0;
if (!mtd_can_have_bb(mtd))
return 0;
pr_info("scanning for bad eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
bbt[i] = is_block_bad(mtd, eb + i) ? 1 : 0;
if (bbt[i])
bad += 1;
cond_resched();
}
pr_info("scanned %d eraseblocks, %d are bad\n", i, bad);
return 0;
}
int mtdtest_erase_good_eraseblocks(struct mtd_info *mtd, unsigned char *bbt,
unsigned int eb, int ebcnt)
{
int err;
unsigned int i;
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = mtdtest_erase_eraseblock(mtd, eb + i);
if (err)
return err;
cond_resched();
}
return 0;
}
int mtdtest_read(struct mtd_info *mtd, loff_t addr, size_t size, void *buf)
{
size_t read;
int err;
err = mtd_read(mtd, addr, size, &read, buf);
/* Ignore corrected ECC errors */
if (mtd_is_bitflip(err))
err = 0;
if (!err && read != size)
err = -EIO;
if (err)
pr_err("error: read failed at %#llx\n", addr);
return err;
}
int mtdtest_write(struct mtd_info *mtd, loff_t addr, size_t size,
const void *buf)
{
size_t written;
int err;
err = mtd_write(mtd, addr, size, &written, buf);
if (!err && written != size)
err = -EIO;
if (err)
pr_err("error: write failed at %#llx\n", addr);
return err;
}

View file

@ -0,0 +1,11 @@
#include <linux/mtd/mtd.h>
int mtdtest_erase_eraseblock(struct mtd_info *mtd, unsigned int ebnum);
int mtdtest_scan_for_bad_eraseblocks(struct mtd_info *mtd, unsigned char *bbt,
unsigned int eb, int ebcnt);
int mtdtest_erase_good_eraseblocks(struct mtd_info *mtd, unsigned char *bbt,
unsigned int eb, int ebcnt);
int mtdtest_read(struct mtd_info *mtd, loff_t addr, size_t size, void *buf);
int mtdtest_write(struct mtd_info *mtd, loff_t addr, size_t size,
const void *buf);

View file

@ -0,0 +1,428 @@
/*
* Copyright © 2012 NetCommWireless
* Iwo Mergler <Iwo.Mergler@netcommwireless.com.au>
*
* Test for multi-bit error recovery on a NAND page This mostly tests the
* ECC controller / driver.
*
* There are two test modes:
*
* 0 - artificially inserting bit errors until the ECC fails
* This is the default method and fairly quick. It should
* be independent of the quality of the FLASH.
*
* 1 - re-writing the same pattern repeatedly until the ECC fails.
* This method relies on the physics of NAND FLASH to eventually
* generate '0' bits if '1' has been written sufficient times.
* Depending on the NAND, the first bit errors will appear after
* 1000 or more writes and then will usually snowball, reaching the
* limits of the ECC quickly.
*
* The test stops after 10000 cycles, should your FLASH be
* exceptionally good and not generate bit errors before that. Try
* a different page in that case.
*
* Please note that neither of these tests will significantly 'use up' any
* FLASH endurance. Only a maximum of two erase operations will be performed.
*
*
* 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/mtd/mtd.h>
#include <linux/err.h>
#include <linux/mtd/nand.h>
#include <linux/slab.h>
#include "mtd_test.h"
static int dev;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static unsigned page_offset;
module_param(page_offset, uint, S_IRUGO);
MODULE_PARM_DESC(page_offset, "Page number relative to dev start");
static unsigned seed;
module_param(seed, uint, S_IRUGO);
MODULE_PARM_DESC(seed, "Random seed");
static int mode;
module_param(mode, int, S_IRUGO);
MODULE_PARM_DESC(mode, "0=incremental errors, 1=overwrite test");
static unsigned max_overwrite = 10000;
static loff_t offset; /* Offset of the page we're using. */
static unsigned eraseblock; /* Eraseblock number for our page. */
/* We assume that the ECC can correct up to a certain number
* of biterrors per subpage. */
static unsigned subsize; /* Size of subpages */
static unsigned subcount; /* Number of subpages per page */
static struct mtd_info *mtd; /* MTD device */
static uint8_t *wbuffer; /* One page write / compare buffer */
static uint8_t *rbuffer; /* One page read buffer */
/* 'random' bytes from known offsets */
static uint8_t hash(unsigned offset)
{
unsigned v = offset;
unsigned char c;
v ^= 0x7f7edfd3;
v = v ^ (v >> 3);
v = v ^ (v >> 5);
v = v ^ (v >> 13);
c = v & 0xFF;
/* Reverse bits of result. */
c = (c & 0x0F) << 4 | (c & 0xF0) >> 4;
c = (c & 0x33) << 2 | (c & 0xCC) >> 2;
c = (c & 0x55) << 1 | (c & 0xAA) >> 1;
return c;
}
/* Writes wbuffer to page */
static int write_page(int log)
{
if (log)
pr_info("write_page\n");
return mtdtest_write(mtd, offset, mtd->writesize, wbuffer);
}
/* Re-writes the data area while leaving the OOB alone. */
static int rewrite_page(int log)
{
int err = 0;
struct mtd_oob_ops ops;
if (log)
pr_info("rewrite page\n");
ops.mode = MTD_OPS_RAW; /* No ECC */
ops.len = mtd->writesize;
ops.retlen = 0;
ops.ooblen = 0;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = wbuffer;
ops.oobbuf = NULL;
err = mtd_write_oob(mtd, offset, &ops);
if (err || ops.retlen != mtd->writesize) {
pr_err("error: write_oob failed (%d)\n", err);
if (!err)
err = -EIO;
}
return err;
}
/* Reads page into rbuffer. Returns number of corrected bit errors (>=0)
* or error (<0) */
static int read_page(int log)
{
int err = 0;
size_t read;
struct mtd_ecc_stats oldstats;
if (log)
pr_info("read_page\n");
/* Saving last mtd stats */
memcpy(&oldstats, &mtd->ecc_stats, sizeof(oldstats));
err = mtd_read(mtd, offset, mtd->writesize, &read, rbuffer);
if (err == -EUCLEAN)
err = mtd->ecc_stats.corrected - oldstats.corrected;
if (err < 0 || read != mtd->writesize) {
pr_err("error: read failed at %#llx\n", (long long)offset);
if (err >= 0)
err = -EIO;
}
return err;
}
/* Verifies rbuffer against random sequence */
static int verify_page(int log)
{
unsigned i, errs = 0;
if (log)
pr_info("verify_page\n");
for (i = 0; i < mtd->writesize; i++) {
if (rbuffer[i] != hash(i+seed)) {
pr_err("Error: page offset %u, expected %02x, got %02x\n",
i, hash(i+seed), rbuffer[i]);
errs++;
}
}
if (errs)
return -EIO;
else
return 0;
}
#define CBIT(v, n) ((v) & (1 << (n)))
#define BCLR(v, n) ((v) = (v) & ~(1 << (n)))
/* Finds the first '1' bit in wbuffer starting at offset 'byte'
* and sets it to '0'. */
static int insert_biterror(unsigned byte)
{
int bit;
while (byte < mtd->writesize) {
for (bit = 7; bit >= 0; bit--) {
if (CBIT(wbuffer[byte], bit)) {
BCLR(wbuffer[byte], bit);
pr_info("Inserted biterror @ %u/%u\n", byte, bit);
return 0;
}
}
byte++;
}
pr_err("biterror: Failed to find a '1' bit\n");
return -EIO;
}
/* Writes 'random' data to page and then introduces deliberate bit
* errors into the page, while verifying each step. */
static int incremental_errors_test(void)
{
int err = 0;
unsigned i;
unsigned errs_per_subpage = 0;
pr_info("incremental biterrors test\n");
for (i = 0; i < mtd->writesize; i++)
wbuffer[i] = hash(i+seed);
err = write_page(1);
if (err)
goto exit;
while (1) {
err = rewrite_page(1);
if (err)
goto exit;
err = read_page(1);
if (err > 0)
pr_info("Read reported %d corrected bit errors\n", err);
if (err < 0) {
pr_err("After %d biterrors per subpage, read reported error %d\n",
errs_per_subpage, err);
err = 0;
goto exit;
}
err = verify_page(1);
if (err) {
pr_err("ECC failure, read data is incorrect despite read success\n");
goto exit;
}
pr_info("Successfully corrected %d bit errors per subpage\n",
errs_per_subpage);
for (i = 0; i < subcount; i++) {
err = insert_biterror(i * subsize);
if (err < 0)
goto exit;
}
errs_per_subpage++;
}
exit:
return err;
}
/* Writes 'random' data to page and then re-writes that same data repeatedly.
This eventually develops bit errors (bits written as '1' will slowly become
'0'), which are corrected as far as the ECC is capable of. */
static int overwrite_test(void)
{
int err = 0;
unsigned i;
unsigned max_corrected = 0;
unsigned opno = 0;
/* We don't expect more than this many correctable bit errors per
* page. */
#define MAXBITS 512
static unsigned bitstats[MAXBITS]; /* bit error histogram. */
memset(bitstats, 0, sizeof(bitstats));
pr_info("overwrite biterrors test\n");
for (i = 0; i < mtd->writesize; i++)
wbuffer[i] = hash(i+seed);
err = write_page(1);
if (err)
goto exit;
while (opno < max_overwrite) {
err = rewrite_page(0);
if (err)
break;
err = read_page(0);
if (err >= 0) {
if (err >= MAXBITS) {
pr_info("Implausible number of bit errors corrected\n");
err = -EIO;
break;
}
bitstats[err]++;
if (err > max_corrected) {
max_corrected = err;
pr_info("Read reported %d corrected bit errors\n",
err);
}
} else { /* err < 0 */
pr_info("Read reported error %d\n", err);
err = 0;
break;
}
err = verify_page(0);
if (err) {
bitstats[max_corrected] = opno;
pr_info("ECC failure, read data is incorrect despite read success\n");
break;
}
opno++;
}
/* At this point bitstats[0] contains the number of ops with no bit
* errors, bitstats[1] the number of ops with 1 bit error, etc. */
pr_info("Bit error histogram (%d operations total):\n", opno);
for (i = 0; i < max_corrected; i++)
pr_info("Page reads with %3d corrected bit errors: %d\n",
i, bitstats[i]);
exit:
return err;
}
static int __init mtd_nandbiterrs_init(void)
{
int err = 0;
printk("\n");
printk(KERN_INFO "==================================================\n");
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
goto exit_mtddev;
}
if (!mtd_type_is_nand(mtd)) {
pr_info("this test requires NAND flash\n");
err = -ENODEV;
goto exit_nand;
}
pr_info("MTD device size %llu, eraseblock=%u, page=%u, oob=%u\n",
(unsigned long long)mtd->size, mtd->erasesize,
mtd->writesize, mtd->oobsize);
subsize = mtd->writesize >> mtd->subpage_sft;
subcount = mtd->writesize / subsize;
pr_info("Device uses %d subpages of %d bytes\n", subcount, subsize);
offset = (loff_t)page_offset * mtd->writesize;
eraseblock = mtd_div_by_eb(offset, mtd);
pr_info("Using page=%u, offset=%llu, eraseblock=%u\n",
page_offset, offset, eraseblock);
wbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
if (!wbuffer) {
err = -ENOMEM;
goto exit_wbuffer;
}
rbuffer = kmalloc(mtd->writesize, GFP_KERNEL);
if (!rbuffer) {
err = -ENOMEM;
goto exit_rbuffer;
}
err = mtdtest_erase_eraseblock(mtd, eraseblock);
if (err)
goto exit_error;
if (mode == 0)
err = incremental_errors_test();
else
err = overwrite_test();
if (err)
goto exit_error;
/* We leave the block un-erased in case of test failure. */
err = mtdtest_erase_eraseblock(mtd, eraseblock);
if (err)
goto exit_error;
err = -EIO;
pr_info("finished successfully.\n");
printk(KERN_INFO "==================================================\n");
exit_error:
kfree(rbuffer);
exit_rbuffer:
kfree(wbuffer);
exit_wbuffer:
/* Nothing */
exit_nand:
put_mtd_device(mtd);
exit_mtddev:
return err;
}
static void __exit mtd_nandbiterrs_exit(void)
{
return;
}
module_init(mtd_nandbiterrs_init);
module_exit(mtd_nandbiterrs_exit);
MODULE_DESCRIPTION("NAND bit error recovery test");
MODULE_AUTHOR("Iwo Mergler");
MODULE_LICENSE("GPL");

649
drivers/mtd/tests/oobtest.c Normal file
View file

@ -0,0 +1,649 @@
/*
* Copyright (C) 2006-2008 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Test OOB read and write on MTD device.
*
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/div64.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/random.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static struct mtd_info *mtd;
static unsigned char *readbuf;
static unsigned char *writebuf;
static unsigned char *bbt;
static int ebcnt;
static int pgcnt;
static int errcnt;
static int use_offset;
static int use_len;
static int use_len_max;
static int vary_offset;
static struct rnd_state rnd_state;
static void do_vary_offset(void)
{
use_len -= 1;
if (use_len < 1) {
use_offset += 1;
if (use_offset >= use_len_max)
use_offset = 0;
use_len = use_len_max - use_offset;
}
}
static int write_eraseblock(int ebnum)
{
int i;
struct mtd_oob_ops ops;
int err = 0;
loff_t addr = ebnum * mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, use_len_max * pgcnt);
for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) {
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = use_len;
ops.oobretlen = 0;
ops.ooboffs = use_offset;
ops.datbuf = NULL;
ops.oobbuf = writebuf + (use_len_max * i) + use_offset;
err = mtd_write_oob(mtd, addr, &ops);
if (err || ops.oobretlen != use_len) {
pr_err("error: writeoob failed at %#llx\n",
(long long)addr);
pr_err("error: use_len %d, use_offset %d\n",
use_len, use_offset);
errcnt += 1;
return err ? err : -1;
}
if (vary_offset)
do_vary_offset();
}
return err;
}
static int write_whole_device(void)
{
int err;
unsigned int i;
pr_info("writing OOBs of whole device\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock(i);
if (err)
return err;
if (i % 256 == 0)
pr_info("written up to eraseblock %u\n", i);
cond_resched();
}
pr_info("written %u eraseblocks\n", i);
return 0;
}
static int verify_eraseblock(int ebnum)
{
int i;
struct mtd_oob_ops ops;
int err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, use_len_max * pgcnt);
for (i = 0; i < pgcnt; ++i, addr += mtd->writesize) {
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = use_len;
ops.oobretlen = 0;
ops.ooboffs = use_offset;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
err = mtd_read_oob(mtd, addr, &ops);
if (err || ops.oobretlen != use_len) {
pr_err("error: readoob failed at %#llx\n",
(long long)addr);
errcnt += 1;
return err ? err : -1;
}
if (memcmp(readbuf, writebuf + (use_len_max * i) + use_offset,
use_len)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too many errors\n");
return -1;
}
}
if (use_offset != 0 || use_len < mtd->ecclayout->oobavail) {
int k;
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
err = mtd_read_oob(mtd, addr, &ops);
if (err || ops.oobretlen != mtd->ecclayout->oobavail) {
pr_err("error: readoob failed at %#llx\n",
(long long)addr);
errcnt += 1;
return err ? err : -1;
}
if (memcmp(readbuf + use_offset,
writebuf + (use_len_max * i) + use_offset,
use_len)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too many errors\n");
return -1;
}
}
for (k = 0; k < use_offset; ++k)
if (readbuf[k] != 0xff) {
pr_err("error: verify 0xff "
"failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too "
"many errors\n");
return -1;
}
}
for (k = use_offset + use_len;
k < mtd->ecclayout->oobavail; ++k)
if (readbuf[k] != 0xff) {
pr_err("error: verify 0xff "
"failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too "
"many errors\n");
return -1;
}
}
}
if (vary_offset)
do_vary_offset();
}
return err;
}
static int verify_eraseblock_in_one_go(int ebnum)
{
struct mtd_oob_ops ops;
int err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
size_t len = mtd->ecclayout->oobavail * pgcnt;
prandom_bytes_state(&rnd_state, writebuf, len);
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = len;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
err = mtd_read_oob(mtd, addr, &ops);
if (err || ops.oobretlen != len) {
pr_err("error: readoob failed at %#llx\n",
(long long)addr);
errcnt += 1;
return err ? err : -1;
}
if (memcmp(readbuf, writebuf, len)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too many errors\n");
return -1;
}
}
return err;
}
static int verify_all_eraseblocks(void)
{
int err;
unsigned int i;
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock(i);
if (err)
return err;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
return 0;
}
static int __init mtd_oobtest_init(void)
{
int err = 0;
unsigned int i;
uint64_t tmp;
struct mtd_oob_ops ops;
loff_t addr = 0, addr0;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (!mtd_type_is_nand(mtd)) {
pr_info("this test requires NAND flash\n");
goto out;
}
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / mtd->writesize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, count of eraseblocks %u, pages per "
"eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
mtd->writesize, ebcnt, pgcnt, mtd->oobsize);
err = -ENOMEM;
readbuf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!readbuf)
goto out;
writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!writebuf)
goto out;
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
use_offset = 0;
use_len = mtd->ecclayout->oobavail;
use_len_max = mtd->ecclayout->oobavail;
vary_offset = 0;
/* First test: write all OOB, read it back and verify */
pr_info("test 1 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
prandom_seed_state(&rnd_state, 1);
err = write_whole_device();
if (err)
goto out;
prandom_seed_state(&rnd_state, 1);
err = verify_all_eraseblocks();
if (err)
goto out;
/*
* Second test: write all OOB, a block at a time, read it back and
* verify.
*/
pr_info("test 2 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
prandom_seed_state(&rnd_state, 3);
err = write_whole_device();
if (err)
goto out;
/* Check all eraseblocks */
prandom_seed_state(&rnd_state, 3);
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock_in_one_go(i);
if (err)
goto out;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
/*
* Third test: write OOB at varying offsets and lengths, read it back
* and verify.
*/
pr_info("test 3 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Write all eraseblocks */
use_offset = 0;
use_len = mtd->ecclayout->oobavail;
use_len_max = mtd->ecclayout->oobavail;
vary_offset = 1;
prandom_seed_state(&rnd_state, 5);
err = write_whole_device();
if (err)
goto out;
/* Check all eraseblocks */
use_offset = 0;
use_len = mtd->ecclayout->oobavail;
use_len_max = mtd->ecclayout->oobavail;
vary_offset = 1;
prandom_seed_state(&rnd_state, 5);
err = verify_all_eraseblocks();
if (err)
goto out;
use_offset = 0;
use_len = mtd->ecclayout->oobavail;
use_len_max = mtd->ecclayout->oobavail;
vary_offset = 0;
/* Fourth test: try to write off end of device */
pr_info("test 4 of 5\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
addr0 = 0;
for (i = 0; i < ebcnt && bbt[i]; ++i)
addr0 += mtd->erasesize;
/* Attempt to write off end of OOB */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = 1;
ops.oobretlen = 0;
ops.ooboffs = mtd->ecclayout->oobavail;
ops.datbuf = NULL;
ops.oobbuf = writebuf;
pr_info("attempting to start write past end of OOB\n");
pr_info("an error is expected...\n");
err = mtd_write_oob(mtd, addr0, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: can write past end of OOB\n");
errcnt += 1;
}
/* Attempt to read off end of OOB */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = 1;
ops.oobretlen = 0;
ops.ooboffs = mtd->ecclayout->oobavail;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
pr_info("attempting to start read past end of OOB\n");
pr_info("an error is expected...\n");
err = mtd_read_oob(mtd, addr0, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: can read past end of OOB\n");
errcnt += 1;
}
if (bbt[ebcnt - 1])
pr_info("skipping end of device tests because last "
"block is bad\n");
else {
/* Attempt to write off end of device */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail + 1;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = writebuf;
pr_info("attempting to write past end of device\n");
pr_info("an error is expected...\n");
err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: wrote past end of device\n");
errcnt += 1;
}
/* Attempt to read off end of device */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail + 1;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
pr_info("attempting to read past end of device\n");
pr_info("an error is expected...\n");
err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: read past end of device\n");
errcnt += 1;
}
err = mtdtest_erase_eraseblock(mtd, ebcnt - 1);
if (err)
goto out;
/* Attempt to write off end of device */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail;
ops.oobretlen = 0;
ops.ooboffs = 1;
ops.datbuf = NULL;
ops.oobbuf = writebuf;
pr_info("attempting to write past end of device\n");
pr_info("an error is expected...\n");
err = mtd_write_oob(mtd, mtd->size - mtd->writesize, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: wrote past end of device\n");
errcnt += 1;
}
/* Attempt to read off end of device */
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail;
ops.oobretlen = 0;
ops.ooboffs = 1;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
pr_info("attempting to read past end of device\n");
pr_info("an error is expected...\n");
err = mtd_read_oob(mtd, mtd->size - mtd->writesize, &ops);
if (err) {
pr_info("error occurred as expected\n");
err = 0;
} else {
pr_err("error: read past end of device\n");
errcnt += 1;
}
}
/* Fifth test: write / read across block boundaries */
pr_info("test 5 of 5\n");
/* Erase all eraseblocks */
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Write all eraseblocks */
prandom_seed_state(&rnd_state, 11);
pr_info("writing OOBs of whole device\n");
for (i = 0; i < ebcnt - 1; ++i) {
int cnt = 2;
int pg;
size_t sz = mtd->ecclayout->oobavail;
if (bbt[i] || bbt[i + 1])
continue;
addr = (loff_t)(i + 1) * mtd->erasesize - mtd->writesize;
prandom_bytes_state(&rnd_state, writebuf, sz * cnt);
for (pg = 0; pg < cnt; ++pg) {
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = sz;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = writebuf + pg * sz;
err = mtd_write_oob(mtd, addr, &ops);
if (err)
goto out;
if (i % 256 == 0)
pr_info("written up to eraseblock %u\n", i);
cond_resched();
addr += mtd->writesize;
}
}
pr_info("written %u eraseblocks\n", i);
/* Check all eraseblocks */
prandom_seed_state(&rnd_state, 11);
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt - 1; ++i) {
if (bbt[i] || bbt[i + 1])
continue;
prandom_bytes_state(&rnd_state, writebuf,
mtd->ecclayout->oobavail * 2);
addr = (loff_t)(i + 1) * mtd->erasesize - mtd->writesize;
ops.mode = MTD_OPS_AUTO_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->ecclayout->oobavail * 2;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = readbuf;
err = mtd_read_oob(mtd, addr, &ops);
if (err)
goto out;
if (memcmp(readbuf, writebuf, mtd->ecclayout->oobavail * 2)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
if (errcnt > 1000) {
pr_err("error: too many errors\n");
goto out;
}
}
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
pr_info("finished with %d errors\n", errcnt);
out:
kfree(bbt);
kfree(writebuf);
kfree(readbuf);
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_oobtest_init);
static void __exit mtd_oobtest_exit(void)
{
return;
}
module_exit(mtd_oobtest_exit);
MODULE_DESCRIPTION("Out-of-band test module");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,464 @@
/*
* Copyright (C) 2006-2008 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Test page read and write on MTD device.
*
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <asm/div64.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/random.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static struct mtd_info *mtd;
static unsigned char *twopages;
static unsigned char *writebuf;
static unsigned char *boundary;
static unsigned char *bbt;
static int pgsize;
static int bufsize;
static int ebcnt;
static int pgcnt;
static int errcnt;
static struct rnd_state rnd_state;
static int write_eraseblock(int ebnum)
{
loff_t addr = (loff_t)ebnum * mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize);
cond_resched();
return mtdtest_write(mtd, addr, mtd->erasesize, writebuf);
}
static int verify_eraseblock(int ebnum)
{
uint32_t j;
int err = 0, i;
loff_t addr0, addrn;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
addr0 = 0;
for (i = 0; i < ebcnt && bbt[i]; ++i)
addr0 += mtd->erasesize;
addrn = mtd->size;
for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
addrn -= mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize);
for (j = 0; j < pgcnt - 1; ++j, addr += pgsize) {
/* Do a read to set the internal dataRAMs to different data */
err = mtdtest_read(mtd, addr0, bufsize, twopages);
if (err)
return err;
err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages);
if (err)
return err;
memset(twopages, 0, bufsize);
err = mtdtest_read(mtd, addr, bufsize, twopages);
if (err)
break;
if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
}
}
/* Check boundary between eraseblocks */
if (addr <= addrn - pgsize - pgsize && !bbt[ebnum + 1]) {
struct rnd_state old_state = rnd_state;
/* Do a read to set the internal dataRAMs to different data */
err = mtdtest_read(mtd, addr0, bufsize, twopages);
if (err)
return err;
err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages);
if (err)
return err;
memset(twopages, 0, bufsize);
err = mtdtest_read(mtd, addr, bufsize, twopages);
if (err)
return err;
memcpy(boundary, writebuf + mtd->erasesize - pgsize, pgsize);
prandom_bytes_state(&rnd_state, boundary + pgsize, pgsize);
if (memcmp(twopages, boundary, bufsize)) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
}
rnd_state = old_state;
}
return err;
}
static int crosstest(void)
{
int err = 0, i;
loff_t addr, addr0, addrn;
unsigned char *pp1, *pp2, *pp3, *pp4;
pr_info("crosstest\n");
pp1 = kmalloc(pgsize * 4, GFP_KERNEL);
if (!pp1)
return -ENOMEM;
pp2 = pp1 + pgsize;
pp3 = pp2 + pgsize;
pp4 = pp3 + pgsize;
memset(pp1, 0, pgsize * 4);
addr0 = 0;
for (i = 0; i < ebcnt && bbt[i]; ++i)
addr0 += mtd->erasesize;
addrn = mtd->size;
for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i)
addrn -= mtd->erasesize;
/* Read 2nd-to-last page to pp1 */
addr = addrn - pgsize - pgsize;
err = mtdtest_read(mtd, addr, pgsize, pp1);
if (err) {
kfree(pp1);
return err;
}
/* Read 3rd-to-last page to pp1 */
addr = addrn - pgsize - pgsize - pgsize;
err = mtdtest_read(mtd, addr, pgsize, pp1);
if (err) {
kfree(pp1);
return err;
}
/* Read first page to pp2 */
addr = addr0;
pr_info("reading page at %#llx\n", (long long)addr);
err = mtdtest_read(mtd, addr, pgsize, pp2);
if (err) {
kfree(pp1);
return err;
}
/* Read last page to pp3 */
addr = addrn - pgsize;
pr_info("reading page at %#llx\n", (long long)addr);
err = mtdtest_read(mtd, addr, pgsize, pp3);
if (err) {
kfree(pp1);
return err;
}
/* Read first page again to pp4 */
addr = addr0;
pr_info("reading page at %#llx\n", (long long)addr);
err = mtdtest_read(mtd, addr, pgsize, pp4);
if (err) {
kfree(pp1);
return err;
}
/* pp2 and pp4 should be the same */
pr_info("verifying pages read at %#llx match\n",
(long long)addr0);
if (memcmp(pp2, pp4, pgsize)) {
pr_err("verify failed!\n");
errcnt += 1;
} else if (!err)
pr_info("crosstest ok\n");
kfree(pp1);
return err;
}
static int erasecrosstest(void)
{
int err = 0, i, ebnum, ebnum2;
loff_t addr0;
char *readbuf = twopages;
pr_info("erasecrosstest\n");
ebnum = 0;
addr0 = 0;
for (i = 0; i < ebcnt && bbt[i]; ++i) {
addr0 += mtd->erasesize;
ebnum += 1;
}
ebnum2 = ebcnt - 1;
while (ebnum2 && bbt[ebnum2])
ebnum2 -= 1;
pr_info("erasing block %d\n", ebnum);
err = mtdtest_erase_eraseblock(mtd, ebnum);
if (err)
return err;
pr_info("writing 1st page of block %d\n", ebnum);
prandom_bytes_state(&rnd_state, writebuf, pgsize);
strcpy(writebuf, "There is no data like this!");
err = mtdtest_write(mtd, addr0, pgsize, writebuf);
if (err)
return err;
pr_info("reading 1st page of block %d\n", ebnum);
memset(readbuf, 0, pgsize);
err = mtdtest_read(mtd, addr0, pgsize, readbuf);
if (err)
return err;
pr_info("verifying 1st page of block %d\n", ebnum);
if (memcmp(writebuf, readbuf, pgsize)) {
pr_err("verify failed!\n");
errcnt += 1;
return -1;
}
pr_info("erasing block %d\n", ebnum);
err = mtdtest_erase_eraseblock(mtd, ebnum);
if (err)
return err;
pr_info("writing 1st page of block %d\n", ebnum);
prandom_bytes_state(&rnd_state, writebuf, pgsize);
strcpy(writebuf, "There is no data like this!");
err = mtdtest_write(mtd, addr0, pgsize, writebuf);
if (err)
return err;
pr_info("erasing block %d\n", ebnum2);
err = mtdtest_erase_eraseblock(mtd, ebnum2);
if (err)
return err;
pr_info("reading 1st page of block %d\n", ebnum);
memset(readbuf, 0, pgsize);
err = mtdtest_read(mtd, addr0, pgsize, readbuf);
if (err)
return err;
pr_info("verifying 1st page of block %d\n", ebnum);
if (memcmp(writebuf, readbuf, pgsize)) {
pr_err("verify failed!\n");
errcnt += 1;
return -1;
}
if (!err)
pr_info("erasecrosstest ok\n");
return err;
}
static int erasetest(void)
{
int err = 0, i, ebnum, ok = 1;
loff_t addr0;
pr_info("erasetest\n");
ebnum = 0;
addr0 = 0;
for (i = 0; i < ebcnt && bbt[i]; ++i) {
addr0 += mtd->erasesize;
ebnum += 1;
}
pr_info("erasing block %d\n", ebnum);
err = mtdtest_erase_eraseblock(mtd, ebnum);
if (err)
return err;
pr_info("writing 1st page of block %d\n", ebnum);
prandom_bytes_state(&rnd_state, writebuf, pgsize);
err = mtdtest_write(mtd, addr0, pgsize, writebuf);
if (err)
return err;
pr_info("erasing block %d\n", ebnum);
err = mtdtest_erase_eraseblock(mtd, ebnum);
if (err)
return err;
pr_info("reading 1st page of block %d\n", ebnum);
err = mtdtest_read(mtd, addr0, pgsize, twopages);
if (err)
return err;
pr_info("verifying 1st page of block %d is all 0xff\n",
ebnum);
for (i = 0; i < pgsize; ++i)
if (twopages[i] != 0xff) {
pr_err("verifying all 0xff failed at %d\n",
i);
errcnt += 1;
ok = 0;
break;
}
if (ok && !err)
pr_info("erasetest ok\n");
return err;
}
static int __init mtd_pagetest_init(void)
{
int err = 0;
uint64_t tmp;
uint32_t i;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (!mtd_type_is_nand(mtd)) {
pr_info("this test requires NAND flash\n");
goto out;
}
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / mtd->writesize;
pgsize = mtd->writesize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, count of eraseblocks %u, pages per "
"eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
pgsize, ebcnt, pgcnt, mtd->oobsize);
err = -ENOMEM;
bufsize = pgsize * 2;
writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!writebuf)
goto out;
twopages = kmalloc(bufsize, GFP_KERNEL);
if (!twopages)
goto out;
boundary = kmalloc(bufsize, GFP_KERNEL);
if (!boundary)
goto out;
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Erase all eraseblocks */
pr_info("erasing whole device\n");
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
pr_info("erased %u eraseblocks\n", ebcnt);
/* Write all eraseblocks */
prandom_seed_state(&rnd_state, 1);
pr_info("writing whole device\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock(i);
if (err)
goto out;
if (i % 256 == 0)
pr_info("written up to eraseblock %u\n", i);
cond_resched();
}
pr_info("written %u eraseblocks\n", i);
/* Check all eraseblocks */
prandom_seed_state(&rnd_state, 1);
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock(i);
if (err)
goto out;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
err = crosstest();
if (err)
goto out;
err = erasecrosstest();
if (err)
goto out;
err = erasetest();
if (err)
goto out;
pr_info("finished with %d errors\n", errcnt);
out:
kfree(bbt);
kfree(boundary);
kfree(twopages);
kfree(writebuf);
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_pagetest_init);
static void __exit mtd_pagetest_exit(void)
{
return;
}
module_exit(mtd_pagetest_exit);
MODULE_DESCRIPTION("NAND page test");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,222 @@
/*
* Copyright (C) 2006-2008 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Check MTD device read.
*
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static struct mtd_info *mtd;
static unsigned char *iobuf;
static unsigned char *iobuf1;
static unsigned char *bbt;
static int pgsize;
static int ebcnt;
static int pgcnt;
static int read_eraseblock_by_page(int ebnum)
{
int i, ret, err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
void *buf = iobuf;
void *oobbuf = iobuf1;
for (i = 0; i < pgcnt; i++) {
memset(buf, 0 , pgsize);
ret = mtdtest_read(mtd, addr, pgsize, buf);
if (ret) {
if (!err)
err = ret;
}
if (mtd->oobsize) {
struct mtd_oob_ops ops;
ops.mode = MTD_OPS_PLACE_OOB;
ops.len = 0;
ops.retlen = 0;
ops.ooblen = mtd->oobsize;
ops.oobretlen = 0;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.oobbuf = oobbuf;
ret = mtd_read_oob(mtd, addr, &ops);
if ((ret && !mtd_is_bitflip(ret)) ||
ops.oobretlen != mtd->oobsize) {
pr_err("error: read oob failed at "
"%#llx\n", (long long)addr);
if (!err)
err = ret;
if (!err)
err = -EINVAL;
}
oobbuf += mtd->oobsize;
}
addr += pgsize;
buf += pgsize;
}
return err;
}
static void dump_eraseblock(int ebnum)
{
int i, j, n;
char line[128];
int pg, oob;
pr_info("dumping eraseblock %d\n", ebnum);
n = mtd->erasesize;
for (i = 0; i < n;) {
char *p = line;
p += sprintf(p, "%05x: ", i);
for (j = 0; j < 32 && i < n; j++, i++)
p += sprintf(p, "%02x", (unsigned int)iobuf[i]);
printk(KERN_CRIT "%s\n", line);
cond_resched();
}
if (!mtd->oobsize)
return;
pr_info("dumping oob from eraseblock %d\n", ebnum);
n = mtd->oobsize;
for (pg = 0, i = 0; pg < pgcnt; pg++)
for (oob = 0; oob < n;) {
char *p = line;
p += sprintf(p, "%05x: ", i);
for (j = 0; j < 32 && oob < n; j++, oob++, i++)
p += sprintf(p, "%02x",
(unsigned int)iobuf1[i]);
printk(KERN_CRIT "%s\n", line);
cond_resched();
}
}
static int __init mtd_readtest_init(void)
{
uint64_t tmp;
int err, i;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: Cannot get MTD device\n");
return err;
}
if (mtd->writesize == 1) {
pr_info("not NAND flash, assume page size is 512 "
"bytes.\n");
pgsize = 512;
} else
pgsize = mtd->writesize;
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / pgsize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, count of eraseblocks %u, pages per "
"eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
pgsize, ebcnt, pgcnt, mtd->oobsize);
err = -ENOMEM;
iobuf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!iobuf)
goto out;
iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!iobuf1)
goto out;
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Read all eraseblocks 1 page at a time */
pr_info("testing page read\n");
for (i = 0; i < ebcnt; ++i) {
int ret;
if (bbt[i])
continue;
ret = read_eraseblock_by_page(i);
if (ret) {
dump_eraseblock(i);
if (!err)
err = ret;
}
cond_resched();
}
if (err)
pr_info("finished with errors\n");
else
pr_info("finished\n");
out:
kfree(iobuf);
kfree(iobuf1);
kfree(bbt);
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_readtest_init);
static void __exit mtd_readtest_exit(void)
{
return;
}
module_exit(mtd_readtest_exit);
MODULE_DESCRIPTION("Read test module");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,416 @@
/*
* Copyright (C) 2007 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; 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: Adrian Hunter <adrian.hunter@nokia.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/random.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static int count;
module_param(count, int, S_IRUGO);
MODULE_PARM_DESC(count, "Maximum number of eraseblocks to use "
"(0 means use all)");
static struct mtd_info *mtd;
static unsigned char *iobuf;
static unsigned char *bbt;
static int pgsize;
static int ebcnt;
static int pgcnt;
static int goodebcnt;
static struct timeval start, finish;
static int multiblock_erase(int ebnum, int blocks)
{
int err;
struct erase_info ei;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
memset(&ei, 0, sizeof(struct erase_info));
ei.mtd = mtd;
ei.addr = addr;
ei.len = mtd->erasesize * blocks;
err = mtd_erase(mtd, &ei);
if (err) {
pr_err("error %d while erasing EB %d, blocks %d\n",
err, ebnum, blocks);
return err;
}
if (ei.state == MTD_ERASE_FAILED) {
pr_err("some erase error occurred at EB %d,"
"blocks %d\n", ebnum, blocks);
return -EIO;
}
return 0;
}
static int write_eraseblock(int ebnum)
{
loff_t addr = (loff_t)ebnum * mtd->erasesize;
return mtdtest_write(mtd, addr, mtd->erasesize, iobuf);
}
static int write_eraseblock_by_page(int ebnum)
{
int i, err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
void *buf = iobuf;
for (i = 0; i < pgcnt; i++) {
err = mtdtest_write(mtd, addr, pgsize, buf);
if (err)
break;
addr += pgsize;
buf += pgsize;
}
return err;
}
static int write_eraseblock_by_2pages(int ebnum)
{
size_t sz = pgsize * 2;
int i, n = pgcnt / 2, err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
void *buf = iobuf;
for (i = 0; i < n; i++) {
err = mtdtest_write(mtd, addr, sz, buf);
if (err)
return err;
addr += sz;
buf += sz;
}
if (pgcnt % 2)
err = mtdtest_write(mtd, addr, pgsize, buf);
return err;
}
static int read_eraseblock(int ebnum)
{
loff_t addr = (loff_t)ebnum * mtd->erasesize;
return mtdtest_read(mtd, addr, mtd->erasesize, iobuf);
}
static int read_eraseblock_by_page(int ebnum)
{
int i, err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
void *buf = iobuf;
for (i = 0; i < pgcnt; i++) {
err = mtdtest_read(mtd, addr, pgsize, buf);
if (err)
break;
addr += pgsize;
buf += pgsize;
}
return err;
}
static int read_eraseblock_by_2pages(int ebnum)
{
size_t sz = pgsize * 2;
int i, n = pgcnt / 2, err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
void *buf = iobuf;
for (i = 0; i < n; i++) {
err = mtdtest_read(mtd, addr, sz, buf);
if (err)
return err;
addr += sz;
buf += sz;
}
if (pgcnt % 2)
err = mtdtest_read(mtd, addr, pgsize, buf);
return err;
}
static inline void start_timing(void)
{
do_gettimeofday(&start);
}
static inline void stop_timing(void)
{
do_gettimeofday(&finish);
}
static long calc_speed(void)
{
uint64_t k;
long ms;
ms = (finish.tv_sec - start.tv_sec) * 1000 +
(finish.tv_usec - start.tv_usec) / 1000;
if (ms == 0)
return 0;
k = goodebcnt * (mtd->erasesize / 1024) * 1000;
do_div(k, ms);
return k;
}
static int __init mtd_speedtest_init(void)
{
int err, i, blocks, j, k;
long speed;
uint64_t tmp;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
if (count)
pr_info("MTD device: %d count: %d\n", dev, count);
else
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (mtd->writesize == 1) {
pr_info("not NAND flash, assume page size is 512 "
"bytes.\n");
pgsize = 512;
} else
pgsize = mtd->writesize;
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / pgsize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, count of eraseblocks %u, pages per "
"eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
pgsize, ebcnt, pgcnt, mtd->oobsize);
if (count > 0 && count < ebcnt)
ebcnt = count;
err = -ENOMEM;
iobuf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!iobuf)
goto out;
prandom_bytes(iobuf, mtd->erasesize);
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
for (i = 0; i < ebcnt; i++) {
if (!bbt[i])
goodebcnt++;
}
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Write all eraseblocks, 1 eraseblock at a time */
pr_info("testing eraseblock write speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("eraseblock write speed is %ld KiB/s\n", speed);
/* Read all eraseblocks, 1 eraseblock at a time */
pr_info("testing eraseblock read speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = read_eraseblock(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("eraseblock read speed is %ld KiB/s\n", speed);
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Write all eraseblocks, 1 page at a time */
pr_info("testing page write speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock_by_page(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("page write speed is %ld KiB/s\n", speed);
/* Read all eraseblocks, 1 page at a time */
pr_info("testing page read speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = read_eraseblock_by_page(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("page read speed is %ld KiB/s\n", speed);
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Write all eraseblocks, 2 pages at a time */
pr_info("testing 2 page write speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock_by_2pages(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("2 page write speed is %ld KiB/s\n", speed);
/* Read all eraseblocks, 2 pages at a time */
pr_info("testing 2 page read speed\n");
start_timing();
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = read_eraseblock_by_2pages(i);
if (err)
goto out;
cond_resched();
}
stop_timing();
speed = calc_speed();
pr_info("2 page read speed is %ld KiB/s\n", speed);
/* Erase all eraseblocks */
pr_info("Testing erase speed\n");
start_timing();
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
stop_timing();
speed = calc_speed();
pr_info("erase speed is %ld KiB/s\n", speed);
/* Multi-block erase all eraseblocks */
for (k = 1; k < 7; k++) {
blocks = 1 << k;
pr_info("Testing %dx multi-block erase speed\n",
blocks);
start_timing();
for (i = 0; i < ebcnt; ) {
for (j = 0; j < blocks && (i + j) < ebcnt; j++)
if (bbt[i + j])
break;
if (j < 1) {
i++;
continue;
}
err = multiblock_erase(i, j);
if (err)
goto out;
cond_resched();
i += j;
}
stop_timing();
speed = calc_speed();
pr_info("%dx multi-block erase speed is %ld KiB/s\n",
blocks, speed);
}
pr_info("finished\n");
out:
kfree(iobuf);
kfree(bbt);
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_speedtest_init);
static void __exit mtd_speedtest_exit(void)
{
return;
}
module_exit(mtd_speedtest_exit);
MODULE_DESCRIPTION("Speed test module");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,250 @@
/*
* Copyright (C) 2006-2008 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Test random reads, writes and erases on MTD device.
*
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/vmalloc.h>
#include <linux/random.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static int count = 10000;
module_param(count, int, S_IRUGO);
MODULE_PARM_DESC(count, "Number of operations to do (default is 10000)");
static struct mtd_info *mtd;
static unsigned char *writebuf;
static unsigned char *readbuf;
static unsigned char *bbt;
static int *offsets;
static int pgsize;
static int bufsize;
static int ebcnt;
static int pgcnt;
static int rand_eb(void)
{
unsigned int eb;
again:
eb = prandom_u32();
/* Read or write up 2 eraseblocks at a time - hence 'ebcnt - 1' */
eb %= (ebcnt - 1);
if (bbt[eb])
goto again;
return eb;
}
static int rand_offs(void)
{
unsigned int offs;
offs = prandom_u32();
offs %= bufsize;
return offs;
}
static int rand_len(int offs)
{
unsigned int len;
len = prandom_u32();
len %= (bufsize - offs);
return len;
}
static int do_read(void)
{
int eb = rand_eb();
int offs = rand_offs();
int len = rand_len(offs);
loff_t addr;
if (bbt[eb + 1]) {
if (offs >= mtd->erasesize)
offs -= mtd->erasesize;
if (offs + len > mtd->erasesize)
len = mtd->erasesize - offs;
}
addr = eb * mtd->erasesize + offs;
return mtdtest_read(mtd, addr, len, readbuf);
}
static int do_write(void)
{
int eb = rand_eb(), offs, err, len;
loff_t addr;
offs = offsets[eb];
if (offs >= mtd->erasesize) {
err = mtdtest_erase_eraseblock(mtd, eb);
if (err)
return err;
offs = offsets[eb] = 0;
}
len = rand_len(offs);
len = ((len + pgsize - 1) / pgsize) * pgsize;
if (offs + len > mtd->erasesize) {
if (bbt[eb + 1])
len = mtd->erasesize - offs;
else {
err = mtdtest_erase_eraseblock(mtd, eb + 1);
if (err)
return err;
offsets[eb + 1] = 0;
}
}
addr = eb * mtd->erasesize + offs;
err = mtdtest_write(mtd, addr, len, writebuf);
if (unlikely(err))
return err;
offs += len;
while (offs > mtd->erasesize) {
offsets[eb++] = mtd->erasesize;
offs -= mtd->erasesize;
}
offsets[eb] = offs;
return 0;
}
static int do_operation(void)
{
if (prandom_u32() & 1)
return do_read();
else
return do_write();
}
static int __init mtd_stresstest_init(void)
{
int err;
int i, op;
uint64_t tmp;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (mtd->writesize == 1) {
pr_info("not NAND flash, assume page size is 512 "
"bytes.\n");
pgsize = 512;
} else
pgsize = mtd->writesize;
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / pgsize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, count of eraseblocks %u, pages per "
"eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
pgsize, ebcnt, pgcnt, mtd->oobsize);
if (ebcnt < 2) {
pr_err("error: need at least 2 eraseblocks\n");
err = -ENOSPC;
goto out_put_mtd;
}
/* Read or write up 2 eraseblocks at a time */
bufsize = mtd->erasesize * 2;
err = -ENOMEM;
readbuf = vmalloc(bufsize);
writebuf = vmalloc(bufsize);
offsets = kmalloc(ebcnt * sizeof(int), GFP_KERNEL);
if (!readbuf || !writebuf || !offsets)
goto out;
for (i = 0; i < ebcnt; i++)
offsets[i] = mtd->erasesize;
prandom_bytes(writebuf, bufsize);
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
/* Do operations */
pr_info("doing operations\n");
for (op = 0; op < count; op++) {
if ((op & 1023) == 0)
pr_info("%d operations done\n", op);
err = do_operation();
if (err)
goto out;
cond_resched();
}
pr_info("finished, %d operations done\n", op);
out:
kfree(offsets);
kfree(bbt);
vfree(writebuf);
vfree(readbuf);
out_put_mtd:
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_stresstest_init);
static void __exit mtd_stresstest_exit(void)
{
return;
}
module_exit(mtd_stresstest_exit);
MODULE_DESCRIPTION("Stress test module");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,435 @@
/*
* Copyright (C) 2006-2007 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; see the file COPYING. If not, write to the Free Software
* Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Test sub-page read and write on MTD device.
* Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/random.h>
#include "mtd_test.h"
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static struct mtd_info *mtd;
static unsigned char *writebuf;
static unsigned char *readbuf;
static unsigned char *bbt;
static int subpgsize;
static int bufsize;
static int ebcnt;
static int pgcnt;
static int errcnt;
static struct rnd_state rnd_state;
static inline void clear_data(unsigned char *buf, size_t len)
{
memset(buf, 0, len);
}
static int write_eraseblock(int ebnum)
{
size_t written;
int err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, subpgsize);
err = mtd_write(mtd, addr, subpgsize, &written, writebuf);
if (unlikely(err || written != subpgsize)) {
pr_err("error: write failed at %#llx\n",
(long long)addr);
if (written != subpgsize) {
pr_err(" write size: %#x\n", subpgsize);
pr_err(" written: %#zx\n", written);
}
return err ? err : -1;
}
addr += subpgsize;
prandom_bytes_state(&rnd_state, writebuf, subpgsize);
err = mtd_write(mtd, addr, subpgsize, &written, writebuf);
if (unlikely(err || written != subpgsize)) {
pr_err("error: write failed at %#llx\n",
(long long)addr);
if (written != subpgsize) {
pr_err(" write size: %#x\n", subpgsize);
pr_err(" written: %#zx\n", written);
}
return err ? err : -1;
}
return err;
}
static int write_eraseblock2(int ebnum)
{
size_t written;
int err = 0, k;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
for (k = 1; k < 33; ++k) {
if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize)
break;
prandom_bytes_state(&rnd_state, writebuf, subpgsize * k);
err = mtd_write(mtd, addr, subpgsize * k, &written, writebuf);
if (unlikely(err || written != subpgsize * k)) {
pr_err("error: write failed at %#llx\n",
(long long)addr);
if (written != subpgsize) {
pr_err(" write size: %#x\n",
subpgsize * k);
pr_err(" written: %#08zx\n",
written);
}
return err ? err : -1;
}
addr += subpgsize * k;
}
return err;
}
static void print_subpage(unsigned char *p)
{
int i, j;
for (i = 0; i < subpgsize; ) {
for (j = 0; i < subpgsize && j < 32; ++i, ++j)
printk("%02x", *p++);
printk("\n");
}
}
static int verify_eraseblock(int ebnum)
{
size_t read;
int err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
prandom_bytes_state(&rnd_state, writebuf, subpgsize);
clear_data(readbuf, subpgsize);
err = mtd_read(mtd, addr, subpgsize, &read, readbuf);
if (unlikely(err || read != subpgsize)) {
if (mtd_is_bitflip(err) && read == subpgsize) {
pr_info("ECC correction at %#llx\n",
(long long)addr);
err = 0;
} else {
pr_err("error: read failed at %#llx\n",
(long long)addr);
return err ? err : -1;
}
}
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
pr_info("------------- written----------------\n");
print_subpage(writebuf);
pr_info("------------- read ------------------\n");
print_subpage(readbuf);
pr_info("-------------------------------------\n");
errcnt += 1;
}
addr += subpgsize;
prandom_bytes_state(&rnd_state, writebuf, subpgsize);
clear_data(readbuf, subpgsize);
err = mtd_read(mtd, addr, subpgsize, &read, readbuf);
if (unlikely(err || read != subpgsize)) {
if (mtd_is_bitflip(err) && read == subpgsize) {
pr_info("ECC correction at %#llx\n",
(long long)addr);
err = 0;
} else {
pr_err("error: read failed at %#llx\n",
(long long)addr);
return err ? err : -1;
}
}
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
pr_info("error: verify failed at %#llx\n",
(long long)addr);
pr_info("------------- written----------------\n");
print_subpage(writebuf);
pr_info("------------- read ------------------\n");
print_subpage(readbuf);
pr_info("-------------------------------------\n");
errcnt += 1;
}
return err;
}
static int verify_eraseblock2(int ebnum)
{
size_t read;
int err = 0, k;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
for (k = 1; k < 33; ++k) {
if (addr + (subpgsize * k) > (ebnum + 1) * mtd->erasesize)
break;
prandom_bytes_state(&rnd_state, writebuf, subpgsize * k);
clear_data(readbuf, subpgsize * k);
err = mtd_read(mtd, addr, subpgsize * k, &read, readbuf);
if (unlikely(err || read != subpgsize * k)) {
if (mtd_is_bitflip(err) && read == subpgsize * k) {
pr_info("ECC correction at %#llx\n",
(long long)addr);
err = 0;
} else {
pr_err("error: read failed at "
"%#llx\n", (long long)addr);
return err ? err : -1;
}
}
if (unlikely(memcmp(readbuf, writebuf, subpgsize * k))) {
pr_err("error: verify failed at %#llx\n",
(long long)addr);
errcnt += 1;
}
addr += subpgsize * k;
}
return err;
}
static int verify_eraseblock_ff(int ebnum)
{
uint32_t j;
size_t read;
int err = 0;
loff_t addr = (loff_t)ebnum * mtd->erasesize;
memset(writebuf, 0xff, subpgsize);
for (j = 0; j < mtd->erasesize / subpgsize; ++j) {
clear_data(readbuf, subpgsize);
err = mtd_read(mtd, addr, subpgsize, &read, readbuf);
if (unlikely(err || read != subpgsize)) {
if (mtd_is_bitflip(err) && read == subpgsize) {
pr_info("ECC correction at %#llx\n",
(long long)addr);
err = 0;
} else {
pr_err("error: read failed at "
"%#llx\n", (long long)addr);
return err ? err : -1;
}
}
if (unlikely(memcmp(readbuf, writebuf, subpgsize))) {
pr_err("error: verify 0xff failed at "
"%#llx\n", (long long)addr);
errcnt += 1;
}
addr += subpgsize;
}
return err;
}
static int verify_all_eraseblocks_ff(void)
{
int err;
unsigned int i;
pr_info("verifying all eraseblocks for 0xff\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock_ff(i);
if (err)
return err;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
return 0;
}
static int __init mtd_subpagetest_init(void)
{
int err = 0;
uint32_t i;
uint64_t tmp;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (!mtd_type_is_nand(mtd)) {
pr_info("this test requires NAND flash\n");
goto out;
}
subpgsize = mtd->writesize >> mtd->subpage_sft;
tmp = mtd->size;
do_div(tmp, mtd->erasesize);
ebcnt = tmp;
pgcnt = mtd->erasesize / mtd->writesize;
pr_info("MTD device size %llu, eraseblock size %u, "
"page size %u, subpage size %u, count of eraseblocks %u, "
"pages per eraseblock %u, OOB size %u\n",
(unsigned long long)mtd->size, mtd->erasesize,
mtd->writesize, subpgsize, ebcnt, pgcnt, mtd->oobsize);
err = -ENOMEM;
bufsize = subpgsize * 32;
writebuf = kmalloc(bufsize, GFP_KERNEL);
if (!writebuf)
goto out;
readbuf = kmalloc(bufsize, GFP_KERNEL);
if (!readbuf)
goto out;
bbt = kzalloc(ebcnt, GFP_KERNEL);
if (!bbt)
goto out;
err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
pr_info("writing whole device\n");
prandom_seed_state(&rnd_state, 1);
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock(i);
if (unlikely(err))
goto out;
if (i % 256 == 0)
pr_info("written up to eraseblock %u\n", i);
cond_resched();
}
pr_info("written %u eraseblocks\n", i);
prandom_seed_state(&rnd_state, 1);
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock(i);
if (unlikely(err))
goto out;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
err = verify_all_eraseblocks_ff();
if (err)
goto out;
/* Write all eraseblocks */
prandom_seed_state(&rnd_state, 3);
pr_info("writing whole device\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = write_eraseblock2(i);
if (unlikely(err))
goto out;
if (i % 256 == 0)
pr_info("written up to eraseblock %u\n", i);
cond_resched();
}
pr_info("written %u eraseblocks\n", i);
/* Check all eraseblocks */
prandom_seed_state(&rnd_state, 3);
pr_info("verifying all eraseblocks\n");
for (i = 0; i < ebcnt; ++i) {
if (bbt[i])
continue;
err = verify_eraseblock2(i);
if (unlikely(err))
goto out;
if (i % 256 == 0)
pr_info("verified up to eraseblock %u\n", i);
cond_resched();
}
pr_info("verified %u eraseblocks\n", i);
err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt);
if (err)
goto out;
err = verify_all_eraseblocks_ff();
if (err)
goto out;
pr_info("finished with %d errors\n", errcnt);
out:
kfree(bbt);
kfree(readbuf);
kfree(writebuf);
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(mtd_subpagetest_init);
static void __exit mtd_subpagetest_exit(void)
{
return;
}
module_exit(mtd_subpagetest_exit);
MODULE_DESCRIPTION("Subpage test module");
MODULE_AUTHOR("Adrian Hunter");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,485 @@
/*
* Copyright (C) 2006-2008 Artem Bityutskiy
* Copyright (C) 2006-2008 Jarkko Lavinen
* Copyright (C) 2006-2008 Adrian Hunter
*
* 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.
*
* Authors: Artem Bityutskiy, Jarkko Lavinen, Adria Hunter
*
* WARNING: this test program may kill your flash and your device. Do not
* use it unless you know what you do. Authors are not responsible for any
* damage caused by this program.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/err.h>
#include <linux/mtd/mtd.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include "mtd_test.h"
#define RETRIES 3
static int eb = 8;
module_param(eb, int, S_IRUGO);
MODULE_PARM_DESC(eb, "eraseblock number within the selected MTD device");
static int ebcnt = 32;
module_param(ebcnt, int, S_IRUGO);
MODULE_PARM_DESC(ebcnt, "number of consecutive eraseblocks to torture");
static int pgcnt;
module_param(pgcnt, int, S_IRUGO);
MODULE_PARM_DESC(pgcnt, "number of pages per eraseblock to torture (0 => all)");
static int dev = -EINVAL;
module_param(dev, int, S_IRUGO);
MODULE_PARM_DESC(dev, "MTD device number to use");
static int gran = 512;
module_param(gran, int, S_IRUGO);
MODULE_PARM_DESC(gran, "how often the status information should be printed");
static int check = 1;
module_param(check, int, S_IRUGO);
MODULE_PARM_DESC(check, "if the written data should be checked");
static unsigned int cycles_count;
module_param(cycles_count, uint, S_IRUGO);
MODULE_PARM_DESC(cycles_count, "how many erase cycles to do "
"(infinite by default)");
static struct mtd_info *mtd;
/* This buffer contains 0x555555...0xAAAAAA... pattern */
static unsigned char *patt_5A5;
/* This buffer contains 0xAAAAAA...0x555555... pattern */
static unsigned char *patt_A5A;
/* This buffer contains all 0xFF bytes */
static unsigned char *patt_FF;
/* This a temporary buffer is use when checking data */
static unsigned char *check_buf;
/* How many erase cycles were done */
static unsigned int erase_cycles;
static int pgsize;
static struct timeval start, finish;
static void report_corrupt(unsigned char *read, unsigned char *written);
static inline void start_timing(void)
{
do_gettimeofday(&start);
}
static inline void stop_timing(void)
{
do_gettimeofday(&finish);
}
/*
* Check that the contents of eraseblock number @enbum is equivalent to the
* @buf buffer.
*/
static inline int check_eraseblock(int ebnum, unsigned char *buf)
{
int err, retries = 0;
size_t read;
loff_t addr = ebnum * mtd->erasesize;
size_t len = mtd->erasesize;
if (pgcnt) {
addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
len = pgcnt * pgsize;
}
retry:
err = mtd_read(mtd, addr, len, &read, check_buf);
if (mtd_is_bitflip(err))
pr_err("single bit flip occurred at EB %d "
"MTD reported that it was fixed.\n", ebnum);
else if (err) {
pr_err("error %d while reading EB %d, "
"read %zd\n", err, ebnum, read);
return err;
}
if (read != len) {
pr_err("failed to read %zd bytes from EB %d, "
"read only %zd, but no error reported\n",
len, ebnum, read);
return -EIO;
}
if (memcmp(buf, check_buf, len)) {
pr_err("read wrong data from EB %d\n", ebnum);
report_corrupt(check_buf, buf);
if (retries++ < RETRIES) {
/* Try read again */
yield();
pr_info("re-try reading data from EB %d\n",
ebnum);
goto retry;
} else {
pr_info("retried %d times, still errors, "
"give-up\n", RETRIES);
return -EINVAL;
}
}
if (retries != 0)
pr_info("only attempt number %d was OK (!!!)\n",
retries);
return 0;
}
static inline int write_pattern(int ebnum, void *buf)
{
int err;
size_t written;
loff_t addr = ebnum * mtd->erasesize;
size_t len = mtd->erasesize;
if (pgcnt) {
addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
len = pgcnt * pgsize;
}
err = mtd_write(mtd, addr, len, &written, buf);
if (err) {
pr_err("error %d while writing EB %d, written %zd"
" bytes\n", err, ebnum, written);
return err;
}
if (written != len) {
pr_info("written only %zd bytes of %zd, but no error"
" reported\n", written, len);
return -EIO;
}
return 0;
}
static int __init tort_init(void)
{
int err = 0, i, infinite = !cycles_count;
unsigned char *bad_ebs;
printk(KERN_INFO "\n");
printk(KERN_INFO "=================================================\n");
pr_info("Warning: this program is trying to wear out your "
"flash, stop it if this is not wanted.\n");
if (dev < 0) {
pr_info("Please specify a valid mtd-device via module parameter\n");
pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
return -EINVAL;
}
pr_info("MTD device: %d\n", dev);
pr_info("torture %d eraseblocks (%d-%d) of mtd%d\n",
ebcnt, eb, eb + ebcnt - 1, dev);
if (pgcnt)
pr_info("torturing just %d pages per eraseblock\n",
pgcnt);
pr_info("write verify %s\n", check ? "enabled" : "disabled");
mtd = get_mtd_device(NULL, dev);
if (IS_ERR(mtd)) {
err = PTR_ERR(mtd);
pr_err("error: cannot get MTD device\n");
return err;
}
if (mtd->writesize == 1) {
pr_info("not NAND flash, assume page size is 512 "
"bytes.\n");
pgsize = 512;
} else
pgsize = mtd->writesize;
if (pgcnt && (pgcnt > mtd->erasesize / pgsize || pgcnt < 0)) {
pr_err("error: invalid pgcnt value %d\n", pgcnt);
goto out_mtd;
}
err = -ENOMEM;
patt_5A5 = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!patt_5A5)
goto out_mtd;
patt_A5A = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!patt_A5A)
goto out_patt_5A5;
patt_FF = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!patt_FF)
goto out_patt_A5A;
check_buf = kmalloc(mtd->erasesize, GFP_KERNEL);
if (!check_buf)
goto out_patt_FF;
bad_ebs = kzalloc(ebcnt, GFP_KERNEL);
if (!bad_ebs)
goto out_check_buf;
err = 0;
/* Initialize patterns */
memset(patt_FF, 0xFF, mtd->erasesize);
for (i = 0; i < mtd->erasesize / pgsize; i++) {
if (!(i & 1)) {
memset(patt_5A5 + i * pgsize, 0x55, pgsize);
memset(patt_A5A + i * pgsize, 0xAA, pgsize);
} else {
memset(patt_5A5 + i * pgsize, 0xAA, pgsize);
memset(patt_A5A + i * pgsize, 0x55, pgsize);
}
}
err = mtdtest_scan_for_bad_eraseblocks(mtd, bad_ebs, eb, ebcnt);
if (err)
goto out;
start_timing();
while (1) {
int i;
void *patt;
err = mtdtest_erase_good_eraseblocks(mtd, bad_ebs, eb, ebcnt);
if (err)
goto out;
/* Check if the eraseblocks contain only 0xFF bytes */
if (check) {
for (i = eb; i < eb + ebcnt; i++) {
if (bad_ebs[i - eb])
continue;
err = check_eraseblock(i, patt_FF);
if (err) {
pr_info("verify failed"
" for 0xFF... pattern\n");
goto out;
}
cond_resched();
}
}
/* Write the pattern */
for (i = eb; i < eb + ebcnt; i++) {
if (bad_ebs[i - eb])
continue;
if ((eb + erase_cycles) & 1)
patt = patt_5A5;
else
patt = patt_A5A;
err = write_pattern(i, patt);
if (err)
goto out;
cond_resched();
}
/* Verify what we wrote */
if (check) {
for (i = eb; i < eb + ebcnt; i++) {
if (bad_ebs[i - eb])
continue;
if ((eb + erase_cycles) & 1)
patt = patt_5A5;
else
patt = patt_A5A;
err = check_eraseblock(i, patt);
if (err) {
pr_info("verify failed for %s"
" pattern\n",
((eb + erase_cycles) & 1) ?
"0x55AA55..." : "0xAA55AA...");
goto out;
}
cond_resched();
}
}
erase_cycles += 1;
if (erase_cycles % gran == 0) {
long ms;
stop_timing();
ms = (finish.tv_sec - start.tv_sec) * 1000 +
(finish.tv_usec - start.tv_usec) / 1000;
pr_info("%08u erase cycles done, took %lu "
"milliseconds (%lu seconds)\n",
erase_cycles, ms, ms / 1000);
start_timing();
}
if (!infinite && --cycles_count == 0)
break;
}
out:
pr_info("finished after %u erase cycles\n",
erase_cycles);
kfree(bad_ebs);
out_check_buf:
kfree(check_buf);
out_patt_FF:
kfree(patt_FF);
out_patt_A5A:
kfree(patt_A5A);
out_patt_5A5:
kfree(patt_5A5);
out_mtd:
put_mtd_device(mtd);
if (err)
pr_info("error %d occurred during torturing\n", err);
printk(KERN_INFO "=================================================\n");
return err;
}
module_init(tort_init);
static void __exit tort_exit(void)
{
return;
}
module_exit(tort_exit);
static int countdiffs(unsigned char *buf, unsigned char *check_buf,
unsigned offset, unsigned len, unsigned *bytesp,
unsigned *bitsp);
static void print_bufs(unsigned char *read, unsigned char *written, int start,
int len);
/*
* Report the detailed information about how the read EB differs from what was
* written.
*/
static void report_corrupt(unsigned char *read, unsigned char *written)
{
int i;
int bytes, bits, pages, first;
int offset, len;
size_t check_len = mtd->erasesize;
if (pgcnt)
check_len = pgcnt * pgsize;
bytes = bits = pages = 0;
for (i = 0; i < check_len; i += pgsize)
if (countdiffs(written, read, i, pgsize, &bytes,
&bits) >= 0)
pages++;
pr_info("verify fails on %d pages, %d bytes/%d bits\n",
pages, bytes, bits);
pr_info("The following is a list of all differences between"
" what was read from flash and what was expected\n");
for (i = 0; i < check_len; i += pgsize) {
cond_resched();
bytes = bits = 0;
first = countdiffs(written, read, i, pgsize, &bytes,
&bits);
if (first < 0)
continue;
printk("-------------------------------------------------------"
"----------------------------------\n");
pr_info("Page %zd has %d bytes/%d bits failing verify,"
" starting at offset 0x%x\n",
(mtd->erasesize - check_len + i) / pgsize,
bytes, bits, first);
offset = first & ~0x7;
len = ((first + bytes) | 0x7) + 1 - offset;
print_bufs(read, written, offset, len);
}
}
static void print_bufs(unsigned char *read, unsigned char *written, int start,
int len)
{
int i = 0, j1, j2;
char *diff;
printk("Offset Read Written\n");
while (i < len) {
printk("0x%08x: ", start + i);
diff = " ";
for (j1 = 0; j1 < 8 && i + j1 < len; j1++) {
printk(" %02x", read[start + i + j1]);
if (read[start + i + j1] != written[start + i + j1])
diff = "***";
}
while (j1 < 8) {
printk(" ");
j1 += 1;
}
printk(" %s ", diff);
for (j2 = 0; j2 < 8 && i + j2 < len; j2++)
printk(" %02x", written[start + i + j2]);
printk("\n");
i += 8;
}
}
/*
* Count the number of differing bytes and bits and return the first differing
* offset.
*/
static int countdiffs(unsigned char *buf, unsigned char *check_buf,
unsigned offset, unsigned len, unsigned *bytesp,
unsigned *bitsp)
{
unsigned i, bit;
int first = -1;
for (i = offset; i < offset + len; i++)
if (buf[i] != check_buf[i]) {
first = i;
break;
}
while (i < offset + len) {
if (buf[i] != check_buf[i]) {
(*bytesp)++;
bit = 1;
while (bit < 256) {
if ((buf[i] & bit) != (check_buf[i] & bit))
(*bitsp)++;
bit <<= 1;
}
}
i++;
}
return first;
}
MODULE_DESCRIPTION("Eraseblock torturing module");
MODULE_AUTHOR("Artem Bityutskiy, Jarkko Lavinen, Adrian Hunter");
MODULE_LICENSE("GPL");