18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2006-2008 Nokia Corporation 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Check MTD device read. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/moduleparam.h> 158c2ecf20Sopenharmony_ci#include <linux/err.h> 168c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci#include <linux/sched.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "mtd_test.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic int dev = -EINVAL; 238c2ecf20Sopenharmony_cimodule_param(dev, int, S_IRUGO); 248c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dev, "MTD device number to use"); 258c2ecf20Sopenharmony_ci 268c2ecf20Sopenharmony_cistatic struct mtd_info *mtd; 278c2ecf20Sopenharmony_cistatic unsigned char *iobuf; 288c2ecf20Sopenharmony_cistatic unsigned char *iobuf1; 298c2ecf20Sopenharmony_cistatic unsigned char *bbt; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int pgsize; 328c2ecf20Sopenharmony_cistatic int ebcnt; 338c2ecf20Sopenharmony_cistatic int pgcnt; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistatic int read_eraseblock_by_page(int ebnum) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci int i, ret, err = 0; 388c2ecf20Sopenharmony_ci loff_t addr = (loff_t)ebnum * mtd->erasesize; 398c2ecf20Sopenharmony_ci void *buf = iobuf; 408c2ecf20Sopenharmony_ci void *oobbuf = iobuf1; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci for (i = 0; i < pgcnt; i++) { 438c2ecf20Sopenharmony_ci memset(buf, 0 , pgsize); 448c2ecf20Sopenharmony_ci ret = mtdtest_read(mtd, addr, pgsize, buf); 458c2ecf20Sopenharmony_ci if (ret) { 468c2ecf20Sopenharmony_ci if (!err) 478c2ecf20Sopenharmony_ci err = ret; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci if (mtd->oobsize) { 508c2ecf20Sopenharmony_ci struct mtd_oob_ops ops; 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci ops.mode = MTD_OPS_PLACE_OOB; 538c2ecf20Sopenharmony_ci ops.len = 0; 548c2ecf20Sopenharmony_ci ops.retlen = 0; 558c2ecf20Sopenharmony_ci ops.ooblen = mtd->oobsize; 568c2ecf20Sopenharmony_ci ops.oobretlen = 0; 578c2ecf20Sopenharmony_ci ops.ooboffs = 0; 588c2ecf20Sopenharmony_ci ops.datbuf = NULL; 598c2ecf20Sopenharmony_ci ops.oobbuf = oobbuf; 608c2ecf20Sopenharmony_ci ret = mtd_read_oob(mtd, addr, &ops); 618c2ecf20Sopenharmony_ci if ((ret && !mtd_is_bitflip(ret)) || 628c2ecf20Sopenharmony_ci ops.oobretlen != mtd->oobsize) { 638c2ecf20Sopenharmony_ci pr_err("error: read oob failed at " 648c2ecf20Sopenharmony_ci "%#llx\n", (long long)addr); 658c2ecf20Sopenharmony_ci if (!err) 668c2ecf20Sopenharmony_ci err = ret; 678c2ecf20Sopenharmony_ci if (!err) 688c2ecf20Sopenharmony_ci err = -EINVAL; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci oobbuf += mtd->oobsize; 718c2ecf20Sopenharmony_ci } 728c2ecf20Sopenharmony_ci addr += pgsize; 738c2ecf20Sopenharmony_ci buf += pgsize; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci return err; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic void dump_eraseblock(int ebnum) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci int i, j, n; 828c2ecf20Sopenharmony_ci char line[128]; 838c2ecf20Sopenharmony_ci int pg, oob; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci pr_info("dumping eraseblock %d\n", ebnum); 868c2ecf20Sopenharmony_ci n = mtd->erasesize; 878c2ecf20Sopenharmony_ci for (i = 0; i < n;) { 888c2ecf20Sopenharmony_ci char *p = line; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci p += sprintf(p, "%05x: ", i); 918c2ecf20Sopenharmony_ci for (j = 0; j < 32 && i < n; j++, i++) 928c2ecf20Sopenharmony_ci p += sprintf(p, "%02x", (unsigned int)iobuf[i]); 938c2ecf20Sopenharmony_ci printk(KERN_CRIT "%s\n", line); 948c2ecf20Sopenharmony_ci cond_resched(); 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci if (!mtd->oobsize) 978c2ecf20Sopenharmony_ci return; 988c2ecf20Sopenharmony_ci pr_info("dumping oob from eraseblock %d\n", ebnum); 998c2ecf20Sopenharmony_ci n = mtd->oobsize; 1008c2ecf20Sopenharmony_ci for (pg = 0, i = 0; pg < pgcnt; pg++) 1018c2ecf20Sopenharmony_ci for (oob = 0; oob < n;) { 1028c2ecf20Sopenharmony_ci char *p = line; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci p += sprintf(p, "%05x: ", i); 1058c2ecf20Sopenharmony_ci for (j = 0; j < 32 && oob < n; j++, oob++, i++) 1068c2ecf20Sopenharmony_ci p += sprintf(p, "%02x", 1078c2ecf20Sopenharmony_ci (unsigned int)iobuf1[i]); 1088c2ecf20Sopenharmony_ci printk(KERN_CRIT "%s\n", line); 1098c2ecf20Sopenharmony_ci cond_resched(); 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic int __init mtd_readtest_init(void) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci uint64_t tmp; 1168c2ecf20Sopenharmony_ci int err, i; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci printk(KERN_INFO "\n"); 1198c2ecf20Sopenharmony_ci printk(KERN_INFO "=================================================\n"); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (dev < 0) { 1228c2ecf20Sopenharmony_ci pr_info("Please specify a valid mtd-device via module parameter\n"); 1238c2ecf20Sopenharmony_ci return -EINVAL; 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci pr_info("MTD device: %d\n", dev); 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci mtd = get_mtd_device(NULL, dev); 1298c2ecf20Sopenharmony_ci if (IS_ERR(mtd)) { 1308c2ecf20Sopenharmony_ci err = PTR_ERR(mtd); 1318c2ecf20Sopenharmony_ci pr_err("error: Cannot get MTD device\n"); 1328c2ecf20Sopenharmony_ci return err; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (mtd->writesize == 1) { 1368c2ecf20Sopenharmony_ci pr_info("not NAND flash, assume page size is 512 " 1378c2ecf20Sopenharmony_ci "bytes.\n"); 1388c2ecf20Sopenharmony_ci pgsize = 512; 1398c2ecf20Sopenharmony_ci } else 1408c2ecf20Sopenharmony_ci pgsize = mtd->writesize; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci tmp = mtd->size; 1438c2ecf20Sopenharmony_ci do_div(tmp, mtd->erasesize); 1448c2ecf20Sopenharmony_ci ebcnt = tmp; 1458c2ecf20Sopenharmony_ci pgcnt = mtd->erasesize / pgsize; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci pr_info("MTD device size %llu, eraseblock size %u, " 1488c2ecf20Sopenharmony_ci "page size %u, count of eraseblocks %u, pages per " 1498c2ecf20Sopenharmony_ci "eraseblock %u, OOB size %u\n", 1508c2ecf20Sopenharmony_ci (unsigned long long)mtd->size, mtd->erasesize, 1518c2ecf20Sopenharmony_ci pgsize, ebcnt, pgcnt, mtd->oobsize); 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci err = -ENOMEM; 1548c2ecf20Sopenharmony_ci iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); 1558c2ecf20Sopenharmony_ci if (!iobuf) 1568c2ecf20Sopenharmony_ci goto out; 1578c2ecf20Sopenharmony_ci iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL); 1588c2ecf20Sopenharmony_ci if (!iobuf1) 1598c2ecf20Sopenharmony_ci goto out; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci bbt = kzalloc(ebcnt, GFP_KERNEL); 1628c2ecf20Sopenharmony_ci if (!bbt) 1638c2ecf20Sopenharmony_ci goto out; 1648c2ecf20Sopenharmony_ci err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); 1658c2ecf20Sopenharmony_ci if (err) 1668c2ecf20Sopenharmony_ci goto out; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci /* Read all eraseblocks 1 page at a time */ 1698c2ecf20Sopenharmony_ci pr_info("testing page read\n"); 1708c2ecf20Sopenharmony_ci for (i = 0; i < ebcnt; ++i) { 1718c2ecf20Sopenharmony_ci int ret; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (bbt[i]) 1748c2ecf20Sopenharmony_ci continue; 1758c2ecf20Sopenharmony_ci ret = read_eraseblock_by_page(i); 1768c2ecf20Sopenharmony_ci if (ret) { 1778c2ecf20Sopenharmony_ci dump_eraseblock(i); 1788c2ecf20Sopenharmony_ci if (!err) 1798c2ecf20Sopenharmony_ci err = ret; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci ret = mtdtest_relax(); 1838c2ecf20Sopenharmony_ci if (ret) { 1848c2ecf20Sopenharmony_ci err = ret; 1858c2ecf20Sopenharmony_ci goto out; 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (err) 1908c2ecf20Sopenharmony_ci pr_info("finished with errors\n"); 1918c2ecf20Sopenharmony_ci else 1928c2ecf20Sopenharmony_ci pr_info("finished\n"); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ciout: 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci kfree(iobuf); 1978c2ecf20Sopenharmony_ci kfree(iobuf1); 1988c2ecf20Sopenharmony_ci kfree(bbt); 1998c2ecf20Sopenharmony_ci put_mtd_device(mtd); 2008c2ecf20Sopenharmony_ci if (err) 2018c2ecf20Sopenharmony_ci pr_info("error %d occurred\n", err); 2028c2ecf20Sopenharmony_ci printk(KERN_INFO "=================================================\n"); 2038c2ecf20Sopenharmony_ci return err; 2048c2ecf20Sopenharmony_ci} 2058c2ecf20Sopenharmony_cimodule_init(mtd_readtest_init); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_cistatic void __exit mtd_readtest_exit(void) 2088c2ecf20Sopenharmony_ci{ 2098c2ecf20Sopenharmony_ci return; 2108c2ecf20Sopenharmony_ci} 2118c2ecf20Sopenharmony_cimodule_exit(mtd_readtest_exit); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Read test module"); 2148c2ecf20Sopenharmony_ciMODULE_AUTHOR("Adrian Hunter"); 2158c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 216