mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
419 lines
9.6 KiB
419 lines
9.6 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright © 2012 NetCommWireless |
|
* Iwo Mergler <[email protected]> |
|
* |
|
* 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. |
|
*/ |
|
|
|
#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/rawnand.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 || 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 = write_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; |
|
} |
|
|
|
err = mtdtest_relax(); |
|
if (err) |
|
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");
|
|
|