162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2006-2008 Artem Bityutskiy
462306a36Sopenharmony_ci * Copyright (C) 2006-2008 Jarkko Lavinen
562306a36Sopenharmony_ci * Copyright (C) 2006-2008 Adrian Hunter
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Authors: Artem Bityutskiy, Jarkko Lavinen, Adria Hunter
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * WARNING: this test program may kill your flash and your device. Do not
1062306a36Sopenharmony_ci * use it unless you know what you do. Authors are not responsible for any
1162306a36Sopenharmony_ci * damage caused by this program.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/init.h>
1762306a36Sopenharmony_ci#include <linux/ktime.h>
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <linux/moduleparam.h>
2062306a36Sopenharmony_ci#include <linux/err.h>
2162306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/sched.h>
2462306a36Sopenharmony_ci#include "mtd_test.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define RETRIES 3
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int eb = 8;
2962306a36Sopenharmony_cimodule_param(eb, int, S_IRUGO);
3062306a36Sopenharmony_ciMODULE_PARM_DESC(eb, "eraseblock number within the selected MTD device");
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic int ebcnt = 32;
3362306a36Sopenharmony_cimodule_param(ebcnt, int, S_IRUGO);
3462306a36Sopenharmony_ciMODULE_PARM_DESC(ebcnt, "number of consecutive eraseblocks to torture");
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic int pgcnt;
3762306a36Sopenharmony_cimodule_param(pgcnt, int, S_IRUGO);
3862306a36Sopenharmony_ciMODULE_PARM_DESC(pgcnt, "number of pages per eraseblock to torture (0 => all)");
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int dev = -EINVAL;
4162306a36Sopenharmony_cimodule_param(dev, int, S_IRUGO);
4262306a36Sopenharmony_ciMODULE_PARM_DESC(dev, "MTD device number to use");
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic int gran = 512;
4562306a36Sopenharmony_cimodule_param(gran, int, S_IRUGO);
4662306a36Sopenharmony_ciMODULE_PARM_DESC(gran, "how often the status information should be printed");
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int check = 1;
4962306a36Sopenharmony_cimodule_param(check, int, S_IRUGO);
5062306a36Sopenharmony_ciMODULE_PARM_DESC(check, "if the written data should be checked");
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic unsigned int cycles_count;
5362306a36Sopenharmony_cimodule_param(cycles_count, uint, S_IRUGO);
5462306a36Sopenharmony_ciMODULE_PARM_DESC(cycles_count, "how many erase cycles to do "
5562306a36Sopenharmony_ci			       "(infinite by default)");
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic struct mtd_info *mtd;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/* This buffer contains 0x555555...0xAAAAAA... pattern */
6062306a36Sopenharmony_cistatic unsigned char *patt_5A5;
6162306a36Sopenharmony_ci/* This buffer contains 0xAAAAAA...0x555555... pattern */
6262306a36Sopenharmony_cistatic unsigned char *patt_A5A;
6362306a36Sopenharmony_ci/* This buffer contains all 0xFF bytes */
6462306a36Sopenharmony_cistatic unsigned char *patt_FF;
6562306a36Sopenharmony_ci/* This a temporary buffer is use when checking data */
6662306a36Sopenharmony_cistatic unsigned char *check_buf;
6762306a36Sopenharmony_ci/* How many erase cycles were done */
6862306a36Sopenharmony_cistatic unsigned int erase_cycles;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int pgsize;
7162306a36Sopenharmony_cistatic ktime_t start, finish;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic void report_corrupt(unsigned char *read, unsigned char *written);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic inline void start_timing(void)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	start = ktime_get();
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic inline void stop_timing(void)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	finish = ktime_get();
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci/*
8662306a36Sopenharmony_ci * Check that the contents of eraseblock number @enbum is equivalent to the
8762306a36Sopenharmony_ci * @buf buffer.
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_cistatic inline int check_eraseblock(int ebnum, unsigned char *buf)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	int err, retries = 0;
9262306a36Sopenharmony_ci	size_t read;
9362306a36Sopenharmony_ci	loff_t addr = (loff_t)ebnum * mtd->erasesize;
9462306a36Sopenharmony_ci	size_t len = mtd->erasesize;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	if (pgcnt) {
9762306a36Sopenharmony_ci		addr = (loff_t)(ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
9862306a36Sopenharmony_ci		len = pgcnt * pgsize;
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ciretry:
10262306a36Sopenharmony_ci	err = mtd_read(mtd, addr, len, &read, check_buf);
10362306a36Sopenharmony_ci	if (mtd_is_bitflip(err))
10462306a36Sopenharmony_ci		pr_err("single bit flip occurred at EB %d "
10562306a36Sopenharmony_ci		       "MTD reported that it was fixed.\n", ebnum);
10662306a36Sopenharmony_ci	else if (err) {
10762306a36Sopenharmony_ci		pr_err("error %d while reading EB %d, "
10862306a36Sopenharmony_ci		       "read %zd\n", err, ebnum, read);
10962306a36Sopenharmony_ci		return err;
11062306a36Sopenharmony_ci	}
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	if (read != len) {
11362306a36Sopenharmony_ci		pr_err("failed to read %zd bytes from EB %d, "
11462306a36Sopenharmony_ci		       "read only %zd, but no error reported\n",
11562306a36Sopenharmony_ci		       len, ebnum, read);
11662306a36Sopenharmony_ci		return -EIO;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (memcmp(buf, check_buf, len)) {
12062306a36Sopenharmony_ci		pr_err("read wrong data from EB %d\n", ebnum);
12162306a36Sopenharmony_ci		report_corrupt(check_buf, buf);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		if (retries++ < RETRIES) {
12462306a36Sopenharmony_ci			/* Try read again */
12562306a36Sopenharmony_ci			yield();
12662306a36Sopenharmony_ci			pr_info("re-try reading data from EB %d\n",
12762306a36Sopenharmony_ci			       ebnum);
12862306a36Sopenharmony_ci			goto retry;
12962306a36Sopenharmony_ci		} else {
13062306a36Sopenharmony_ci			pr_info("retried %d times, still errors, "
13162306a36Sopenharmony_ci			       "give-up\n", RETRIES);
13262306a36Sopenharmony_ci			return -EINVAL;
13362306a36Sopenharmony_ci		}
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (retries != 0)
13762306a36Sopenharmony_ci		pr_info("only attempt number %d was OK (!!!)\n",
13862306a36Sopenharmony_ci		       retries);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	return 0;
14162306a36Sopenharmony_ci}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistatic inline int write_pattern(int ebnum, void *buf)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	int err;
14662306a36Sopenharmony_ci	size_t written;
14762306a36Sopenharmony_ci	loff_t addr = (loff_t)ebnum * mtd->erasesize;
14862306a36Sopenharmony_ci	size_t len = mtd->erasesize;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (pgcnt) {
15162306a36Sopenharmony_ci		addr = (loff_t)(ebnum + 1) * mtd->erasesize - pgcnt * pgsize;
15262306a36Sopenharmony_ci		len = pgcnt * pgsize;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci	err = mtd_write(mtd, addr, len, &written, buf);
15562306a36Sopenharmony_ci	if (err) {
15662306a36Sopenharmony_ci		pr_err("error %d while writing EB %d, written %zd"
15762306a36Sopenharmony_ci		      " bytes\n", err, ebnum, written);
15862306a36Sopenharmony_ci		return err;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci	if (written != len) {
16162306a36Sopenharmony_ci		pr_info("written only %zd bytes of %zd, but no error"
16262306a36Sopenharmony_ci		       " reported\n", written, len);
16362306a36Sopenharmony_ci		return -EIO;
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return 0;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int __init tort_init(void)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	int err = 0, i, infinite = !cycles_count;
17262306a36Sopenharmony_ci	unsigned char *bad_ebs;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	printk(KERN_INFO "\n");
17562306a36Sopenharmony_ci	printk(KERN_INFO "=================================================\n");
17662306a36Sopenharmony_ci	pr_info("Warning: this program is trying to wear out your "
17762306a36Sopenharmony_ci	       "flash, stop it if this is not wanted.\n");
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (dev < 0) {
18062306a36Sopenharmony_ci		pr_info("Please specify a valid mtd-device via module parameter\n");
18162306a36Sopenharmony_ci		pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n");
18262306a36Sopenharmony_ci		return -EINVAL;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	pr_info("MTD device: %d\n", dev);
18662306a36Sopenharmony_ci	pr_info("torture %d eraseblocks (%d-%d) of mtd%d\n",
18762306a36Sopenharmony_ci	       ebcnt, eb, eb + ebcnt - 1, dev);
18862306a36Sopenharmony_ci	if (pgcnt)
18962306a36Sopenharmony_ci		pr_info("torturing just %d pages per eraseblock\n",
19062306a36Sopenharmony_ci			pgcnt);
19162306a36Sopenharmony_ci	pr_info("write verify %s\n", check ? "enabled" : "disabled");
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	mtd = get_mtd_device(NULL, dev);
19462306a36Sopenharmony_ci	if (IS_ERR(mtd)) {
19562306a36Sopenharmony_ci		err = PTR_ERR(mtd);
19662306a36Sopenharmony_ci		pr_err("error: cannot get MTD device\n");
19762306a36Sopenharmony_ci		return err;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (mtd->writesize == 1) {
20162306a36Sopenharmony_ci		pr_info("not NAND flash, assume page size is 512 "
20262306a36Sopenharmony_ci		       "bytes.\n");
20362306a36Sopenharmony_ci		pgsize = 512;
20462306a36Sopenharmony_ci	} else
20562306a36Sopenharmony_ci		pgsize = mtd->writesize;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	if (pgcnt && (pgcnt > mtd->erasesize / pgsize || pgcnt < 0)) {
20862306a36Sopenharmony_ci		pr_err("error: invalid pgcnt value %d\n", pgcnt);
20962306a36Sopenharmony_ci		goto out_mtd;
21062306a36Sopenharmony_ci	}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	err = -ENOMEM;
21362306a36Sopenharmony_ci	patt_5A5 = kmalloc(mtd->erasesize, GFP_KERNEL);
21462306a36Sopenharmony_ci	if (!patt_5A5)
21562306a36Sopenharmony_ci		goto out_mtd;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	patt_A5A = kmalloc(mtd->erasesize, GFP_KERNEL);
21862306a36Sopenharmony_ci	if (!patt_A5A)
21962306a36Sopenharmony_ci		goto out_patt_5A5;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	patt_FF = kmalloc(mtd->erasesize, GFP_KERNEL);
22262306a36Sopenharmony_ci	if (!patt_FF)
22362306a36Sopenharmony_ci		goto out_patt_A5A;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	check_buf = kmalloc(mtd->erasesize, GFP_KERNEL);
22662306a36Sopenharmony_ci	if (!check_buf)
22762306a36Sopenharmony_ci		goto out_patt_FF;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	bad_ebs = kzalloc(ebcnt, GFP_KERNEL);
23062306a36Sopenharmony_ci	if (!bad_ebs)
23162306a36Sopenharmony_ci		goto out_check_buf;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/* Initialize patterns */
23462306a36Sopenharmony_ci	memset(patt_FF, 0xFF, mtd->erasesize);
23562306a36Sopenharmony_ci	for (i = 0; i < mtd->erasesize / pgsize; i++) {
23662306a36Sopenharmony_ci		if (!(i & 1)) {
23762306a36Sopenharmony_ci			memset(patt_5A5 + i * pgsize, 0x55, pgsize);
23862306a36Sopenharmony_ci			memset(patt_A5A + i * pgsize, 0xAA, pgsize);
23962306a36Sopenharmony_ci		} else {
24062306a36Sopenharmony_ci			memset(patt_5A5 + i * pgsize, 0xAA, pgsize);
24162306a36Sopenharmony_ci			memset(patt_A5A + i * pgsize, 0x55, pgsize);
24262306a36Sopenharmony_ci		}
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	err = mtdtest_scan_for_bad_eraseblocks(mtd, bad_ebs, eb, ebcnt);
24662306a36Sopenharmony_ci	if (err)
24762306a36Sopenharmony_ci		goto out;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	start_timing();
25062306a36Sopenharmony_ci	while (1) {
25162306a36Sopenharmony_ci		int i;
25262306a36Sopenharmony_ci		void *patt;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci		err = mtdtest_erase_good_eraseblocks(mtd, bad_ebs, eb, ebcnt);
25562306a36Sopenharmony_ci		if (err)
25662306a36Sopenharmony_ci			goto out;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci		/* Check if the eraseblocks contain only 0xFF bytes */
25962306a36Sopenharmony_ci		if (check) {
26062306a36Sopenharmony_ci			for (i = eb; i < eb + ebcnt; i++) {
26162306a36Sopenharmony_ci				if (bad_ebs[i - eb])
26262306a36Sopenharmony_ci					continue;
26362306a36Sopenharmony_ci				err = check_eraseblock(i, patt_FF);
26462306a36Sopenharmony_ci				if (err) {
26562306a36Sopenharmony_ci					pr_info("verify failed"
26662306a36Sopenharmony_ci					       " for 0xFF... pattern\n");
26762306a36Sopenharmony_ci					goto out;
26862306a36Sopenharmony_ci				}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci				err = mtdtest_relax();
27162306a36Sopenharmony_ci				if (err)
27262306a36Sopenharmony_ci					goto out;
27362306a36Sopenharmony_ci			}
27462306a36Sopenharmony_ci		}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci		/* Write the pattern */
27762306a36Sopenharmony_ci		for (i = eb; i < eb + ebcnt; i++) {
27862306a36Sopenharmony_ci			if (bad_ebs[i - eb])
27962306a36Sopenharmony_ci				continue;
28062306a36Sopenharmony_ci			if ((eb + erase_cycles) & 1)
28162306a36Sopenharmony_ci				patt = patt_5A5;
28262306a36Sopenharmony_ci			else
28362306a36Sopenharmony_ci				patt = patt_A5A;
28462306a36Sopenharmony_ci			err = write_pattern(i, patt);
28562306a36Sopenharmony_ci			if (err)
28662306a36Sopenharmony_ci				goto out;
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci			err = mtdtest_relax();
28962306a36Sopenharmony_ci			if (err)
29062306a36Sopenharmony_ci				goto out;
29162306a36Sopenharmony_ci		}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci		/* Verify what we wrote */
29462306a36Sopenharmony_ci		if (check) {
29562306a36Sopenharmony_ci			for (i = eb; i < eb + ebcnt; i++) {
29662306a36Sopenharmony_ci				if (bad_ebs[i - eb])
29762306a36Sopenharmony_ci					continue;
29862306a36Sopenharmony_ci				if ((eb + erase_cycles) & 1)
29962306a36Sopenharmony_ci					patt = patt_5A5;
30062306a36Sopenharmony_ci				else
30162306a36Sopenharmony_ci					patt = patt_A5A;
30262306a36Sopenharmony_ci				err = check_eraseblock(i, patt);
30362306a36Sopenharmony_ci				if (err) {
30462306a36Sopenharmony_ci					pr_info("verify failed for %s"
30562306a36Sopenharmony_ci					       " pattern\n",
30662306a36Sopenharmony_ci					       ((eb + erase_cycles) & 1) ?
30762306a36Sopenharmony_ci					       "0x55AA55..." : "0xAA55AA...");
30862306a36Sopenharmony_ci					goto out;
30962306a36Sopenharmony_ci				}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci				err = mtdtest_relax();
31262306a36Sopenharmony_ci				if (err)
31362306a36Sopenharmony_ci					goto out;
31462306a36Sopenharmony_ci			}
31562306a36Sopenharmony_ci		}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		erase_cycles += 1;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci		if (erase_cycles % gran == 0) {
32062306a36Sopenharmony_ci			long ms;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci			stop_timing();
32362306a36Sopenharmony_ci			ms = ktime_ms_delta(finish, start);
32462306a36Sopenharmony_ci			pr_info("%08u erase cycles done, took %lu "
32562306a36Sopenharmony_ci			       "milliseconds (%lu seconds)\n",
32662306a36Sopenharmony_ci			       erase_cycles, ms, ms / 1000);
32762306a36Sopenharmony_ci			start_timing();
32862306a36Sopenharmony_ci		}
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci		if (!infinite && --cycles_count == 0)
33162306a36Sopenharmony_ci			break;
33262306a36Sopenharmony_ci	}
33362306a36Sopenharmony_ciout:
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	pr_info("finished after %u erase cycles\n",
33662306a36Sopenharmony_ci	       erase_cycles);
33762306a36Sopenharmony_ci	kfree(bad_ebs);
33862306a36Sopenharmony_ciout_check_buf:
33962306a36Sopenharmony_ci	kfree(check_buf);
34062306a36Sopenharmony_ciout_patt_FF:
34162306a36Sopenharmony_ci	kfree(patt_FF);
34262306a36Sopenharmony_ciout_patt_A5A:
34362306a36Sopenharmony_ci	kfree(patt_A5A);
34462306a36Sopenharmony_ciout_patt_5A5:
34562306a36Sopenharmony_ci	kfree(patt_5A5);
34662306a36Sopenharmony_ciout_mtd:
34762306a36Sopenharmony_ci	put_mtd_device(mtd);
34862306a36Sopenharmony_ci	if (err)
34962306a36Sopenharmony_ci		pr_info("error %d occurred during torturing\n", err);
35062306a36Sopenharmony_ci	printk(KERN_INFO "=================================================\n");
35162306a36Sopenharmony_ci	return err;
35262306a36Sopenharmony_ci}
35362306a36Sopenharmony_cimodule_init(tort_init);
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_cistatic void __exit tort_exit(void)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	return;
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_cimodule_exit(tort_exit);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cistatic int countdiffs(unsigned char *buf, unsigned char *check_buf,
36262306a36Sopenharmony_ci		      unsigned offset, unsigned len, unsigned *bytesp,
36362306a36Sopenharmony_ci		      unsigned *bitsp);
36462306a36Sopenharmony_cistatic void print_bufs(unsigned char *read, unsigned char *written, int start,
36562306a36Sopenharmony_ci		       int len);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci/*
36862306a36Sopenharmony_ci * Report the detailed information about how the read EB differs from what was
36962306a36Sopenharmony_ci * written.
37062306a36Sopenharmony_ci */
37162306a36Sopenharmony_cistatic void report_corrupt(unsigned char *read, unsigned char *written)
37262306a36Sopenharmony_ci{
37362306a36Sopenharmony_ci	int i;
37462306a36Sopenharmony_ci	int bytes, bits, pages, first;
37562306a36Sopenharmony_ci	int offset, len;
37662306a36Sopenharmony_ci	size_t check_len = mtd->erasesize;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (pgcnt)
37962306a36Sopenharmony_ci		check_len = pgcnt * pgsize;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	bytes = bits = pages = 0;
38262306a36Sopenharmony_ci	for (i = 0; i < check_len; i += pgsize)
38362306a36Sopenharmony_ci		if (countdiffs(written, read, i, pgsize, &bytes,
38462306a36Sopenharmony_ci			       &bits) >= 0)
38562306a36Sopenharmony_ci			pages++;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	pr_info("verify fails on %d pages, %d bytes/%d bits\n",
38862306a36Sopenharmony_ci	       pages, bytes, bits);
38962306a36Sopenharmony_ci	pr_info("The following is a list of all differences between"
39062306a36Sopenharmony_ci	       " what was read from flash and what was expected\n");
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	for (i = 0; i < check_len; i += pgsize) {
39362306a36Sopenharmony_ci		cond_resched();
39462306a36Sopenharmony_ci		bytes = bits = 0;
39562306a36Sopenharmony_ci		first = countdiffs(written, read, i, pgsize, &bytes,
39662306a36Sopenharmony_ci				   &bits);
39762306a36Sopenharmony_ci		if (first < 0)
39862306a36Sopenharmony_ci			continue;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci		printk("-------------------------------------------------------"
40162306a36Sopenharmony_ci		       "----------------------------------\n");
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci		pr_info("Page %zd has %d bytes/%d bits failing verify,"
40462306a36Sopenharmony_ci		       " starting at offset 0x%x\n",
40562306a36Sopenharmony_ci		       (mtd->erasesize - check_len + i) / pgsize,
40662306a36Sopenharmony_ci		       bytes, bits, first);
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci		offset = first & ~0x7;
40962306a36Sopenharmony_ci		len = ((first + bytes) | 0x7) + 1 - offset;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci		print_bufs(read, written, offset, len);
41262306a36Sopenharmony_ci	}
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cistatic void print_bufs(unsigned char *read, unsigned char *written, int start,
41662306a36Sopenharmony_ci		       int len)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	int i = 0, j1, j2;
41962306a36Sopenharmony_ci	char *diff;
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	printk("Offset       Read                          Written\n");
42262306a36Sopenharmony_ci	while (i < len) {
42362306a36Sopenharmony_ci		printk("0x%08x: ", start + i);
42462306a36Sopenharmony_ci		diff = "   ";
42562306a36Sopenharmony_ci		for (j1 = 0; j1 < 8 && i + j1 < len; j1++) {
42662306a36Sopenharmony_ci			printk(" %02x", read[start + i + j1]);
42762306a36Sopenharmony_ci			if (read[start + i + j1] != written[start + i + j1])
42862306a36Sopenharmony_ci				diff = "***";
42962306a36Sopenharmony_ci		}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci		while (j1 < 8) {
43262306a36Sopenharmony_ci			printk(" ");
43362306a36Sopenharmony_ci			j1 += 1;
43462306a36Sopenharmony_ci		}
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_ci		printk("  %s ", diff);
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci		for (j2 = 0; j2 < 8 && i + j2 < len; j2++)
43962306a36Sopenharmony_ci			printk(" %02x", written[start + i + j2]);
44062306a36Sopenharmony_ci		printk("\n");
44162306a36Sopenharmony_ci		i += 8;
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci}
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci/*
44662306a36Sopenharmony_ci * Count the number of differing bytes and bits and return the first differing
44762306a36Sopenharmony_ci * offset.
44862306a36Sopenharmony_ci */
44962306a36Sopenharmony_cistatic int countdiffs(unsigned char *buf, unsigned char *check_buf,
45062306a36Sopenharmony_ci		      unsigned offset, unsigned len, unsigned *bytesp,
45162306a36Sopenharmony_ci		      unsigned *bitsp)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	unsigned i, bit;
45462306a36Sopenharmony_ci	int first = -1;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	for (i = offset; i < offset + len; i++)
45762306a36Sopenharmony_ci		if (buf[i] != check_buf[i]) {
45862306a36Sopenharmony_ci			first = i;
45962306a36Sopenharmony_ci			break;
46062306a36Sopenharmony_ci		}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_ci	while (i < offset + len) {
46362306a36Sopenharmony_ci		if (buf[i] != check_buf[i]) {
46462306a36Sopenharmony_ci			(*bytesp)++;
46562306a36Sopenharmony_ci			bit = 1;
46662306a36Sopenharmony_ci			while (bit < 256) {
46762306a36Sopenharmony_ci				if ((buf[i] & bit) != (check_buf[i] & bit))
46862306a36Sopenharmony_ci					(*bitsp)++;
46962306a36Sopenharmony_ci				bit <<= 1;
47062306a36Sopenharmony_ci			}
47162306a36Sopenharmony_ci		}
47262306a36Sopenharmony_ci		i++;
47362306a36Sopenharmony_ci	}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	return first;
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ciMODULE_DESCRIPTION("Eraseblock torturing module");
47962306a36Sopenharmony_ciMODULE_AUTHOR("Artem Bityutskiy, Jarkko Lavinen, Adrian Hunter");
48062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
481