1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2008 Nokia Corporation |
4 | * |
5 | * Check MTD device read. |
6 | * |
7 | * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> |
8 | */ |
9 | |
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
11 | |
12 | #include <linux/init.h> |
13 | #include <linux/module.h> |
14 | #include <linux/moduleparam.h> |
15 | #include <linux/err.h> |
16 | #include <linux/mtd/mtd.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/sched.h> |
19 | |
20 | #include "mtd_test.h" |
21 | |
22 | static int dev = -EINVAL; |
23 | module_param(dev, int, S_IRUGO); |
24 | MODULE_PARM_DESC(dev, "MTD device number to use" ); |
25 | |
26 | static struct mtd_info *mtd; |
27 | static unsigned char *iobuf; |
28 | static unsigned char *iobuf1; |
29 | static unsigned char *bbt; |
30 | |
31 | static int pgsize; |
32 | static int ebcnt; |
33 | static int pgcnt; |
34 | |
35 | static int read_eraseblock_by_page(int ebnum) |
36 | { |
37 | int i, ret, err = 0; |
38 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
39 | void *buf = iobuf; |
40 | void *oobbuf = iobuf1; |
41 | |
42 | for (i = 0; i < pgcnt; i++) { |
43 | memset(buf, 0 , pgsize); |
44 | ret = mtdtest_read(mtd, addr, size: pgsize, buf); |
45 | if (ret) { |
46 | if (!err) |
47 | err = ret; |
48 | } |
49 | if (mtd->oobsize) { |
50 | struct mtd_oob_ops ops = { }; |
51 | |
52 | ops.mode = MTD_OPS_PLACE_OOB; |
53 | ops.len = 0; |
54 | ops.retlen = 0; |
55 | ops.ooblen = mtd->oobsize; |
56 | ops.oobretlen = 0; |
57 | ops.ooboffs = 0; |
58 | ops.datbuf = NULL; |
59 | ops.oobbuf = oobbuf; |
60 | ret = mtd_read_oob(mtd, from: addr, ops: &ops); |
61 | if ((ret && !mtd_is_bitflip(err: ret)) || |
62 | ops.oobretlen != mtd->oobsize) { |
63 | pr_err("error: read oob failed at " |
64 | "%#llx\n" , (long long)addr); |
65 | if (!err) |
66 | err = ret; |
67 | if (!err) |
68 | err = -EINVAL; |
69 | } |
70 | oobbuf += mtd->oobsize; |
71 | } |
72 | addr += pgsize; |
73 | buf += pgsize; |
74 | } |
75 | |
76 | return err; |
77 | } |
78 | |
79 | static void dump_eraseblock(int ebnum) |
80 | { |
81 | int i, j, n; |
82 | char line[128]; |
83 | int pg, oob; |
84 | |
85 | pr_info("dumping eraseblock %d\n" , ebnum); |
86 | n = mtd->erasesize; |
87 | for (i = 0; i < n;) { |
88 | char *p = line; |
89 | |
90 | p += sprintf(buf: p, fmt: "%05x: " , i); |
91 | for (j = 0; j < 32 && i < n; j++, i++) |
92 | p += sprintf(buf: p, fmt: "%02x" , (unsigned int)iobuf[i]); |
93 | printk(KERN_CRIT "%s\n" , line); |
94 | cond_resched(); |
95 | } |
96 | if (!mtd->oobsize) |
97 | return; |
98 | pr_info("dumping oob from eraseblock %d\n" , ebnum); |
99 | n = mtd->oobsize; |
100 | for (pg = 0, i = 0; pg < pgcnt; pg++) |
101 | for (oob = 0; oob < n;) { |
102 | char *p = line; |
103 | |
104 | p += sprintf(buf: p, fmt: "%05x: " , i); |
105 | for (j = 0; j < 32 && oob < n; j++, oob++, i++) |
106 | p += sprintf(buf: p, fmt: "%02x" , |
107 | (unsigned int)iobuf1[i]); |
108 | printk(KERN_CRIT "%s\n" , line); |
109 | cond_resched(); |
110 | } |
111 | } |
112 | |
113 | static int __init mtd_readtest_init(void) |
114 | { |
115 | uint64_t tmp; |
116 | int err, i; |
117 | |
118 | printk(KERN_INFO "\n" ); |
119 | printk(KERN_INFO "=================================================\n" ); |
120 | |
121 | if (dev < 0) { |
122 | pr_info("Please specify a valid mtd-device via module parameter\n" ); |
123 | return -EINVAL; |
124 | } |
125 | |
126 | pr_info("MTD device: %d\n" , dev); |
127 | |
128 | mtd = get_mtd_device(NULL, num: dev); |
129 | if (IS_ERR(ptr: mtd)) { |
130 | err = PTR_ERR(ptr: mtd); |
131 | pr_err("error: Cannot get MTD device\n" ); |
132 | return err; |
133 | } |
134 | |
135 | if (mtd->writesize == 1) { |
136 | pr_info("not NAND flash, assume page size is 512 " |
137 | "bytes.\n" ); |
138 | pgsize = 512; |
139 | } else |
140 | pgsize = mtd->writesize; |
141 | |
142 | tmp = mtd->size; |
143 | do_div(tmp, mtd->erasesize); |
144 | ebcnt = tmp; |
145 | pgcnt = mtd->erasesize / pgsize; |
146 | |
147 | pr_info("MTD device size %llu, eraseblock size %u, " |
148 | "page size %u, count of eraseblocks %u, pages per " |
149 | "eraseblock %u, OOB size %u\n" , |
150 | (unsigned long long)mtd->size, mtd->erasesize, |
151 | pgsize, ebcnt, pgcnt, mtd->oobsize); |
152 | |
153 | err = -ENOMEM; |
154 | iobuf = kmalloc(size: mtd->erasesize, GFP_KERNEL); |
155 | if (!iobuf) |
156 | goto out; |
157 | iobuf1 = kmalloc(size: mtd->erasesize, GFP_KERNEL); |
158 | if (!iobuf1) |
159 | goto out; |
160 | |
161 | bbt = kzalloc(size: ebcnt, GFP_KERNEL); |
162 | if (!bbt) |
163 | goto out; |
164 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
165 | if (err) |
166 | goto out; |
167 | |
168 | /* Read all eraseblocks 1 page at a time */ |
169 | pr_info("testing page read\n" ); |
170 | for (i = 0; i < ebcnt; ++i) { |
171 | int ret; |
172 | |
173 | if (bbt[i]) |
174 | continue; |
175 | ret = read_eraseblock_by_page(ebnum: i); |
176 | if (ret) { |
177 | dump_eraseblock(ebnum: i); |
178 | if (!err) |
179 | err = ret; |
180 | } |
181 | |
182 | ret = mtdtest_relax(); |
183 | if (ret) { |
184 | err = ret; |
185 | goto out; |
186 | } |
187 | } |
188 | |
189 | if (err) |
190 | pr_info("finished with errors\n" ); |
191 | else |
192 | pr_info("finished\n" ); |
193 | |
194 | out: |
195 | |
196 | kfree(objp: iobuf); |
197 | kfree(objp: iobuf1); |
198 | kfree(objp: bbt); |
199 | put_mtd_device(mtd); |
200 | if (err) |
201 | pr_info("error %d occurred\n" , err); |
202 | printk(KERN_INFO "=================================================\n" ); |
203 | return err; |
204 | } |
205 | module_init(mtd_readtest_init); |
206 | |
207 | static void __exit mtd_readtest_exit(void) |
208 | { |
209 | return; |
210 | } |
211 | module_exit(mtd_readtest_exit); |
212 | |
213 | MODULE_DESCRIPTION("Read test module" ); |
214 | MODULE_AUTHOR("Adrian Hunter" ); |
215 | MODULE_LICENSE("GPL" ); |
216 | |