2019-05-30 02:57:41 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2012-08-31 02:59:48 +04:00
/*
* 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 .
*/
2012-10-10 21:34:41 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2012-08-31 02:59:48 +04:00
# include <linux/init.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/mtd/mtd.h>
# include <linux/err.h>
2017-08-04 18:29:10 +03:00
# include <linux/mtd/rawnand.h>
2012-08-31 02:59:48 +04:00
# include <linux/slab.h>
2013-08-03 13:52:16 +04:00
# include "mtd_test.h"
2012-08-31 02:59:48 +04:00
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 )
2012-10-10 21:34:41 +04:00
pr_info ( " write_page \n " ) ;
2012-08-31 02:59:48 +04:00
2013-08-15 17:55:09 +04:00
return mtdtest_write ( mtd , offset , mtd - > writesize , wbuffer ) ;
2012-08-31 02:59:48 +04:00
}
/* 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 )
2012-10-10 21:34:41 +04:00
pr_info ( " rewrite page \n " ) ;
2012-08-31 02:59:48 +04:00
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 ) {
2012-10-10 21:34:41 +04:00
pr_err ( " error: write_oob failed (%d) \n " , err ) ;
2012-08-31 02:59:48 +04:00
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 )
2012-10-10 21:34:41 +04:00
pr_info ( " read_page \n " ) ;
2012-08-31 02:59:48 +04:00
/* Saving last mtd stats */
memcpy ( & oldstats , & mtd - > ecc_stats , sizeof ( oldstats ) ) ;
err = mtd_read ( mtd , offset , mtd - > writesize , & read , rbuffer ) ;
2018-01-09 12:47:02 +03:00
if ( ! err | | err = = - EUCLEAN )
2012-08-31 02:59:48 +04:00
err = mtd - > ecc_stats . corrected - oldstats . corrected ;
if ( err < 0 | | read ! = mtd - > writesize ) {
2012-10-10 21:34:41 +04:00
pr_err ( " error: read failed at %#llx \n " , ( long long ) offset ) ;
2012-08-31 02:59:48 +04:00
if ( err > = 0 )
err = - EIO ;
}
return err ;
}
/* Verifies rbuffer against random sequence */
static int verify_page ( int log )
{
unsigned i , errs = 0 ;
if ( log )
2012-10-10 21:34:41 +04:00
pr_info ( " verify_page \n " ) ;
2012-08-31 02:59:48 +04:00
for ( i = 0 ; i < mtd - > writesize ; i + + ) {
if ( rbuffer [ i ] ! = hash ( i + seed ) ) {
2012-10-10 21:34:41 +04:00
pr_err ( " Error: page offset %u, expected %02x, got %02x \n " ,
2012-08-31 02:59:48 +04:00
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 ) ;
2012-10-10 21:34:41 +04:00
pr_info ( " Inserted biterror @ %u/%u \n " , byte , bit ) ;
2012-08-31 02:59:48 +04:00
return 0 ;
}
}
byte + + ;
}
2012-10-10 21:34:41 +04:00
pr_err ( " biterror: Failed to find a '1' bit \n " ) ;
2012-08-31 02:59:48 +04:00
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 ;
2012-10-10 21:34:41 +04:00
pr_info ( " incremental biterrors test \n " ) ;
2012-08-31 02:59:48 +04:00
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 )
2012-10-10 21:34:41 +04:00
pr_info ( " Read reported %d corrected bit errors \n " , err ) ;
2012-08-31 02:59:48 +04:00
if ( err < 0 ) {
2012-10-10 21:34:41 +04:00
pr_err ( " After %d biterrors per subpage, read reported error %d \n " ,
2012-08-31 02:59:48 +04:00
errs_per_subpage , err ) ;
err = 0 ;
goto exit ;
}
err = verify_page ( 1 ) ;
if ( err ) {
2012-10-10 21:34:41 +04:00
pr_err ( " ECC failure, read data is incorrect despite read success \n " ) ;
2012-08-31 02:59:48 +04:00
goto exit ;
}
2012-10-10 21:34:41 +04:00
pr_info ( " Successfully corrected %d bit errors per subpage \n " ,
2012-08-31 02:59:48 +04:00
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 ) ) ;
2012-10-10 21:34:41 +04:00
pr_info ( " overwrite biterrors test \n " ) ;
2012-08-31 02:59:48 +04:00
for ( i = 0 ; i < mtd - > writesize ; i + + )
wbuffer [ i ] = hash ( i + seed ) ;
err = write_page ( 1 ) ;
if ( err )
goto exit ;
while ( opno < max_overwrite ) {
2016-05-11 09:54:57 +03:00
err = write_page ( 0 ) ;
2012-08-31 02:59:48 +04:00
if ( err )
break ;
err = read_page ( 0 ) ;
if ( err > = 0 ) {
if ( err > = MAXBITS ) {
2012-10-10 21:34:41 +04:00
pr_info ( " Implausible number of bit errors corrected \n " ) ;
2012-08-31 02:59:48 +04:00
err = - EIO ;
break ;
}
bitstats [ err ] + + ;
if ( err > max_corrected ) {
max_corrected = err ;
2012-10-10 21:34:41 +04:00
pr_info ( " Read reported %d corrected bit errors \n " ,
2012-08-31 02:59:48 +04:00
err ) ;
}
} else { /* err < 0 */
2012-10-10 21:34:41 +04:00
pr_info ( " Read reported error %d \n " , err ) ;
2012-08-31 02:59:48 +04:00
err = 0 ;
break ;
}
err = verify_page ( 0 ) ;
if ( err ) {
bitstats [ max_corrected ] = opno ;
2012-10-10 21:34:41 +04:00
pr_info ( " ECC failure, read data is incorrect despite read success \n " ) ;
2012-08-31 02:59:48 +04:00
break ;
}
2015-03-29 22:52:06 +03:00
err = mtdtest_relax ( ) ;
if ( err )
break ;
2012-08-31 02:59:48 +04:00
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 . */
2012-10-10 21:34:41 +04:00
pr_info ( " Bit error histogram (%d operations total): \n " , opno ) ;
2012-08-31 02:59:48 +04:00
for ( i = 0 ; i < max_corrected ; i + + )
2012-10-10 21:34:41 +04:00
pr_info ( " Page reads with %3d corrected bit errors: %d \n " ,
2012-08-31 02:59:48 +04:00
i , bitstats [ i ] ) ;
exit :
return err ;
}
static int __init mtd_nandbiterrs_init ( void )
{
int err = 0 ;
2012-10-10 21:34:41 +04:00
printk ( " \n " ) ;
printk ( KERN_INFO " ================================================== \n " ) ;
pr_info ( " MTD device: %d \n " , dev ) ;
2012-08-31 02:59:48 +04:00
mtd = get_mtd_device ( NULL , dev ) ;
if ( IS_ERR ( mtd ) ) {
err = PTR_ERR ( mtd ) ;
2012-10-10 21:34:41 +04:00
pr_err ( " error: cannot get MTD device \n " ) ;
2012-08-31 02:59:48 +04:00
goto exit_mtddev ;
}
2013-09-25 10:58:17 +04:00
if ( ! mtd_type_is_nand ( mtd ) ) {
2012-10-10 21:34:41 +04:00
pr_info ( " this test requires NAND flash \n " ) ;
2012-08-31 02:59:48 +04:00
err = - ENODEV ;
goto exit_nand ;
}
2012-10-10 21:34:41 +04:00
pr_info ( " MTD device size %llu, eraseblock=%u, page=%u, oob=%u \n " ,
2012-08-31 02:59:48 +04:00
( unsigned long long ) mtd - > size , mtd - > erasesize ,
mtd - > writesize , mtd - > oobsize ) ;
subsize = mtd - > writesize > > mtd - > subpage_sft ;
subcount = mtd - > writesize / subsize ;
2012-10-10 21:34:41 +04:00
pr_info ( " Device uses %d subpages of %d bytes \n " , subcount , subsize ) ;
2012-08-31 02:59:48 +04:00
2014-07-22 06:07:12 +04:00
offset = ( loff_t ) page_offset * mtd - > writesize ;
2012-08-31 02:59:48 +04:00
eraseblock = mtd_div_by_eb ( offset , mtd ) ;
2012-10-10 21:34:41 +04:00
pr_info ( " Using page=%u, offset=%llu, eraseblock=%u \n " ,
2012-08-31 02:59:48 +04:00
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 ;
}
2013-08-03 13:52:16 +04:00
err = mtdtest_erase_eraseblock ( mtd , eraseblock ) ;
2012-08-31 02:59:48 +04:00
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. */
2013-08-03 13:52:16 +04:00
err = mtdtest_erase_eraseblock ( mtd , eraseblock ) ;
2012-08-31 02:59:48 +04:00
if ( err )
goto exit_error ;
err = - EIO ;
2012-10-10 21:34:41 +04:00
pr_info ( " finished successfully. \n " ) ;
printk ( KERN_INFO " ================================================== \n " ) ;
2012-08-31 02:59:48 +04:00
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 " ) ;