162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2006-2008 Nokia Corporation 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Check MTD device read. 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/moduleparam.h> 1562306a36Sopenharmony_ci#include <linux/err.h> 1662306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/sched.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "mtd_test.h" 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic int dev = -EINVAL; 2362306a36Sopenharmony_cimodule_param(dev, int, S_IRUGO); 2462306a36Sopenharmony_ciMODULE_PARM_DESC(dev, "MTD device number to use"); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cistatic struct mtd_info *mtd; 2762306a36Sopenharmony_cistatic unsigned char *iobuf; 2862306a36Sopenharmony_cistatic unsigned char *iobuf1; 2962306a36Sopenharmony_cistatic unsigned char *bbt; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic int pgsize; 3262306a36Sopenharmony_cistatic int ebcnt; 3362306a36Sopenharmony_cistatic int pgcnt; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic int read_eraseblock_by_page(int ebnum) 3662306a36Sopenharmony_ci{ 3762306a36Sopenharmony_ci int i, ret, err = 0; 3862306a36Sopenharmony_ci loff_t addr = (loff_t)ebnum * mtd->erasesize; 3962306a36Sopenharmony_ci void *buf = iobuf; 4062306a36Sopenharmony_ci void *oobbuf = iobuf1; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci for (i = 0; i < pgcnt; i++) { 4362306a36Sopenharmony_ci memset(buf, 0 , pgsize); 4462306a36Sopenharmony_ci ret = mtdtest_read(mtd, addr, pgsize, buf); 4562306a36Sopenharmony_ci if (ret) { 4662306a36Sopenharmony_ci if (!err) 4762306a36Sopenharmony_ci err = ret; 4862306a36Sopenharmony_ci } 4962306a36Sopenharmony_ci if (mtd->oobsize) { 5062306a36Sopenharmony_ci struct mtd_oob_ops ops = { }; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci ops.mode = MTD_OPS_PLACE_OOB; 5362306a36Sopenharmony_ci ops.len = 0; 5462306a36Sopenharmony_ci ops.retlen = 0; 5562306a36Sopenharmony_ci ops.ooblen = mtd->oobsize; 5662306a36Sopenharmony_ci ops.oobretlen = 0; 5762306a36Sopenharmony_ci ops.ooboffs = 0; 5862306a36Sopenharmony_ci ops.datbuf = NULL; 5962306a36Sopenharmony_ci ops.oobbuf = oobbuf; 6062306a36Sopenharmony_ci ret = mtd_read_oob(mtd, addr, &ops); 6162306a36Sopenharmony_ci if ((ret && !mtd_is_bitflip(ret)) || 6262306a36Sopenharmony_ci ops.oobretlen != mtd->oobsize) { 6362306a36Sopenharmony_ci pr_err("error: read oob failed at " 6462306a36Sopenharmony_ci "%#llx\n", (long long)addr); 6562306a36Sopenharmony_ci if (!err) 6662306a36Sopenharmony_ci err = ret; 6762306a36Sopenharmony_ci if (!err) 6862306a36Sopenharmony_ci err = -EINVAL; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci oobbuf += mtd->oobsize; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci addr += pgsize; 7362306a36Sopenharmony_ci buf += pgsize; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci return err; 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic void dump_eraseblock(int ebnum) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci int i, j, n; 8262306a36Sopenharmony_ci char line[128]; 8362306a36Sopenharmony_ci int pg, oob; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci pr_info("dumping eraseblock %d\n", ebnum); 8662306a36Sopenharmony_ci n = mtd->erasesize; 8762306a36Sopenharmony_ci for (i = 0; i < n;) { 8862306a36Sopenharmony_ci char *p = line; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci p += sprintf(p, "%05x: ", i); 9162306a36Sopenharmony_ci for (j = 0; j < 32 && i < n; j++, i++) 9262306a36Sopenharmony_ci p += sprintf(p, "%02x", (unsigned int)iobuf[i]); 9362306a36Sopenharmony_ci printk(KERN_CRIT "%s\n", line); 9462306a36Sopenharmony_ci cond_resched(); 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci if (!mtd->oobsize) 9762306a36Sopenharmony_ci return; 9862306a36Sopenharmony_ci pr_info("dumping oob from eraseblock %d\n", ebnum); 9962306a36Sopenharmony_ci n = mtd->oobsize; 10062306a36Sopenharmony_ci for (pg = 0, i = 0; pg < pgcnt; pg++) 10162306a36Sopenharmony_ci for (oob = 0; oob < n;) { 10262306a36Sopenharmony_ci char *p = line; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci p += sprintf(p, "%05x: ", i); 10562306a36Sopenharmony_ci for (j = 0; j < 32 && oob < n; j++, oob++, i++) 10662306a36Sopenharmony_ci p += sprintf(p, "%02x", 10762306a36Sopenharmony_ci (unsigned int)iobuf1[i]); 10862306a36Sopenharmony_ci printk(KERN_CRIT "%s\n", line); 10962306a36Sopenharmony_ci cond_resched(); 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int __init mtd_readtest_init(void) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci uint64_t tmp; 11662306a36Sopenharmony_ci int err, i; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci printk(KERN_INFO "\n"); 11962306a36Sopenharmony_ci printk(KERN_INFO "=================================================\n"); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci if (dev < 0) { 12262306a36Sopenharmony_ci pr_info("Please specify a valid mtd-device via module parameter\n"); 12362306a36Sopenharmony_ci return -EINVAL; 12462306a36Sopenharmony_ci } 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci pr_info("MTD device: %d\n", dev); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci mtd = get_mtd_device(NULL, dev); 12962306a36Sopenharmony_ci if (IS_ERR(mtd)) { 13062306a36Sopenharmony_ci err = PTR_ERR(mtd); 13162306a36Sopenharmony_ci pr_err("error: Cannot get MTD device\n"); 13262306a36Sopenharmony_ci return err; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (mtd->writesize == 1) { 13662306a36Sopenharmony_ci pr_info("not NAND flash, assume page size is 512 " 13762306a36Sopenharmony_ci "bytes.\n"); 13862306a36Sopenharmony_ci pgsize = 512; 13962306a36Sopenharmony_ci } else 14062306a36Sopenharmony_ci pgsize = mtd->writesize; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci tmp = mtd->size; 14362306a36Sopenharmony_ci do_div(tmp, mtd->erasesize); 14462306a36Sopenharmony_ci ebcnt = tmp; 14562306a36Sopenharmony_ci pgcnt = mtd->erasesize / pgsize; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci pr_info("MTD device size %llu, eraseblock size %u, " 14862306a36Sopenharmony_ci "page size %u, count of eraseblocks %u, pages per " 14962306a36Sopenharmony_ci "eraseblock %u, OOB size %u\n", 15062306a36Sopenharmony_ci (unsigned long long)mtd->size, mtd->erasesize, 15162306a36Sopenharmony_ci pgsize, ebcnt, pgcnt, mtd->oobsize); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci err = -ENOMEM; 15462306a36Sopenharmony_ci iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); 15562306a36Sopenharmony_ci if (!iobuf) 15662306a36Sopenharmony_ci goto out; 15762306a36Sopenharmony_ci iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL); 15862306a36Sopenharmony_ci if (!iobuf1) 15962306a36Sopenharmony_ci goto out; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci bbt = kzalloc(ebcnt, GFP_KERNEL); 16262306a36Sopenharmony_ci if (!bbt) 16362306a36Sopenharmony_ci goto out; 16462306a36Sopenharmony_ci err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); 16562306a36Sopenharmony_ci if (err) 16662306a36Sopenharmony_ci goto out; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci /* Read all eraseblocks 1 page at a time */ 16962306a36Sopenharmony_ci pr_info("testing page read\n"); 17062306a36Sopenharmony_ci for (i = 0; i < ebcnt; ++i) { 17162306a36Sopenharmony_ci int ret; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (bbt[i]) 17462306a36Sopenharmony_ci continue; 17562306a36Sopenharmony_ci ret = read_eraseblock_by_page(i); 17662306a36Sopenharmony_ci if (ret) { 17762306a36Sopenharmony_ci dump_eraseblock(i); 17862306a36Sopenharmony_ci if (!err) 17962306a36Sopenharmony_ci err = ret; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci ret = mtdtest_relax(); 18362306a36Sopenharmony_ci if (ret) { 18462306a36Sopenharmony_ci err = ret; 18562306a36Sopenharmony_ci goto out; 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (err) 19062306a36Sopenharmony_ci pr_info("finished with errors\n"); 19162306a36Sopenharmony_ci else 19262306a36Sopenharmony_ci pr_info("finished\n"); 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ciout: 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci kfree(iobuf); 19762306a36Sopenharmony_ci kfree(iobuf1); 19862306a36Sopenharmony_ci kfree(bbt); 19962306a36Sopenharmony_ci put_mtd_device(mtd); 20062306a36Sopenharmony_ci if (err) 20162306a36Sopenharmony_ci pr_info("error %d occurred\n", err); 20262306a36Sopenharmony_ci printk(KERN_INFO "=================================================\n"); 20362306a36Sopenharmony_ci return err; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_cimodule_init(mtd_readtest_init); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic void __exit mtd_readtest_exit(void) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci return; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_cimodule_exit(mtd_readtest_exit); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ciMODULE_DESCRIPTION("Read test module"); 21462306a36Sopenharmony_ciMODULE_AUTHOR("Adrian Hunter"); 21562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 216