]>
Commit | Line | Data |
---|---|---|
bf60862a AB |
1 | /* |
2 | * Copyright (C) 2006-2007 Nokia Corporation | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License version 2 as published by | |
6 | * the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
11 | * more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License along with | |
14 | * this program; see the file COPYING. If not, write to the Free Software | |
15 | * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
16 | * | |
17 | * Test sub-page read and write on MTD device. | |
18 | * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> | |
19 | * | |
20 | */ | |
21 | ||
cd66a2df VN |
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
23 | ||
bf60862a AB |
24 | #include <linux/init.h> |
25 | #include <linux/module.h> | |
26 | #include <linux/moduleparam.h> | |
27 | #include <linux/err.h> | |
28 | #include <linux/mtd/mtd.h> | |
5a0e3ad6 | 29 | #include <linux/slab.h> |
bf60862a | 30 | #include <linux/sched.h> |
a312b78b | 31 | #include <linux/random.h> |
bf60862a | 32 | |
725cd71c AM |
33 | #include "mtd_test.h" |
34 | ||
7406060e | 35 | static int dev = -EINVAL; |
bf60862a AB |
36 | module_param(dev, int, S_IRUGO); |
37 | MODULE_PARM_DESC(dev, "MTD device number to use"); | |
38 | ||
39 | static struct mtd_info *mtd; | |
40 | static unsigned char *writebuf; | |
41 | static unsigned char *readbuf; | |
42 | static unsigned char *bbt; | |
43 | ||
44 | static int subpgsize; | |
45 | static int bufsize; | |
46 | static int ebcnt; | |
47 | static int pgcnt; | |
48 | static int errcnt; | |
a312b78b | 49 | static struct rnd_state rnd_state; |
bf60862a AB |
50 | |
51 | static inline void clear_data(unsigned char *buf, size_t len) | |
52 | { | |
53 | memset(buf, 0, len); | |
54 | } | |
55 | ||
bf60862a AB |
56 | static int write_eraseblock(int ebnum) |
57 | { | |
30fa9848 | 58 | size_t written; |
bf60862a | 59 | int err = 0; |
1001ff7a | 60 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
bf60862a | 61 | |
a312b78b | 62 | prandom_bytes_state(&rnd_state, writebuf, subpgsize); |
eda95cbf | 63 | err = mtd_write(mtd, addr, subpgsize, &written, writebuf); |
bf60862a | 64 | if (unlikely(err || written != subpgsize)) { |
cd66a2df | 65 | pr_err("error: write failed at %#llx\n", |
bf60862a AB |
66 | (long long)addr); |
67 | if (written != subpgsize) { | |
cd66a2df VN |
68 | pr_err(" write size: %#x\n", subpgsize); |
69 | pr_err(" written: %#zx\n", written); | |
bf60862a AB |
70 | } |
71 | return err ? err : -1; | |
72 | } | |
73 | ||
74 | addr += subpgsize; | |
75 | ||
a312b78b | 76 | prandom_bytes_state(&rnd_state, writebuf, subpgsize); |
eda95cbf | 77 | err = mtd_write(mtd, addr, subpgsize, &written, writebuf); |
bf60862a | 78 | if (unlikely(err || written != subpgsize)) { |
cd66a2df | 79 | pr_err("error: write failed at %#llx\n", |
bf60862a AB |
80 | (long long)addr); |
81 | if (written != subpgsize) { | |
cd66a2df VN |
82 | pr_err(" write size: %#x\n", subpgsize); |
83 | pr_err(" written: %#zx\n", written); | |
bf60862a AB |
84 | } |
85 | return err ? err : -1; | |
86 | } | |
87 | ||
88 | return err; | |
89 | } | |
90 | ||
91 | static int write_eraseblock2(int ebnum) | |
92 | { | |
30fa9848 | 93 | size_t written; |
bf60862a | 94 | int err = 0, k; |
1001ff7a | 95 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
bf60862a AB |
96 | |
97 | for (k = 1; k < 33; ++k) { | |
b9da8bae | 98 | if (addr + (subpgsize * k) > (loff_t)(ebnum + 1) * mtd->erasesize) |
bf60862a | 99 | break; |
a312b78b | 100 | prandom_bytes_state(&rnd_state, writebuf, subpgsize * k); |
eda95cbf | 101 | err = mtd_write(mtd, addr, subpgsize * k, &written, writebuf); |
bf60862a | 102 | if (unlikely(err || written != subpgsize * k)) { |
cd66a2df | 103 | pr_err("error: write failed at %#llx\n", |
bf60862a AB |
104 | (long long)addr); |
105 | if (written != subpgsize) { | |
cd66a2df | 106 | pr_err(" write size: %#x\n", |
bf60862a | 107 | subpgsize * k); |
cd66a2df | 108 | pr_err(" written: %#08zx\n", |
bf60862a AB |
109 | written); |
110 | } | |
111 | return err ? err : -1; | |
112 | } | |
113 | addr += subpgsize * k; | |
114 | } | |
115 | ||
116 | return err; | |
117 | } | |
118 | ||
119 | static void print_subpage(unsigned char *p) | |
120 | { | |
121 | int i, j; | |
122 | ||
123 | for (i = 0; i < subpgsize; ) { | |
124 | for (j = 0; i < subpgsize && j < 32; ++i, ++j) | |
125 | printk("%02x", *p++); | |
126 | printk("\n"); | |
127 | } | |
128 | } | |
129 | ||
130 | static int verify_eraseblock(int ebnum) | |
131 | { | |
30fa9848 | 132 | size_t read; |
bf60862a | 133 | int err = 0; |
1001ff7a | 134 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
bf60862a | 135 | |
a312b78b | 136 | prandom_bytes_state(&rnd_state, writebuf, subpgsize); |
bf60862a | 137 | clear_data(readbuf, subpgsize); |
329ad399 | 138 | err = mtd_read(mtd, addr, subpgsize, &read, readbuf); |
bf60862a | 139 | if (unlikely(err || read != subpgsize)) { |
d57f4054 | 140 | if (mtd_is_bitflip(err) && read == subpgsize) { |
cd66a2df | 141 | pr_info("ECC correction at %#llx\n", |
bf60862a AB |
142 | (long long)addr); |
143 | err = 0; | |
144 | } else { | |
cd66a2df | 145 | pr_err("error: read failed at %#llx\n", |
bf60862a AB |
146 | (long long)addr); |
147 | return err ? err : -1; | |
148 | } | |
149 | } | |
150 | if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { | |
cd66a2df | 151 | pr_err("error: verify failed at %#llx\n", |
bf60862a | 152 | (long long)addr); |
cd66a2df | 153 | pr_info("------------- written----------------\n"); |
bf60862a | 154 | print_subpage(writebuf); |
cd66a2df | 155 | pr_info("------------- read ------------------\n"); |
bf60862a | 156 | print_subpage(readbuf); |
cd66a2df | 157 | pr_info("-------------------------------------\n"); |
bf60862a AB |
158 | errcnt += 1; |
159 | } | |
160 | ||
161 | addr += subpgsize; | |
162 | ||
a312b78b | 163 | prandom_bytes_state(&rnd_state, writebuf, subpgsize); |
bf60862a | 164 | clear_data(readbuf, subpgsize); |
329ad399 | 165 | err = mtd_read(mtd, addr, subpgsize, &read, readbuf); |
bf60862a | 166 | if (unlikely(err || read != subpgsize)) { |
d57f4054 | 167 | if (mtd_is_bitflip(err) && read == subpgsize) { |
cd66a2df | 168 | pr_info("ECC correction at %#llx\n", |
bf60862a AB |
169 | (long long)addr); |
170 | err = 0; | |
171 | } else { | |
cd66a2df | 172 | pr_err("error: read failed at %#llx\n", |
bf60862a AB |
173 | (long long)addr); |
174 | return err ? err : -1; | |
175 | } | |
176 | } | |
177 | if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { | |
cd66a2df | 178 | pr_info("error: verify failed at %#llx\n", |
bf60862a | 179 | (long long)addr); |
cd66a2df | 180 | pr_info("------------- written----------------\n"); |
bf60862a | 181 | print_subpage(writebuf); |
cd66a2df | 182 | pr_info("------------- read ------------------\n"); |
bf60862a | 183 | print_subpage(readbuf); |
cd66a2df | 184 | pr_info("-------------------------------------\n"); |
bf60862a AB |
185 | errcnt += 1; |
186 | } | |
187 | ||
188 | return err; | |
189 | } | |
190 | ||
191 | static int verify_eraseblock2(int ebnum) | |
192 | { | |
30fa9848 | 193 | size_t read; |
bf60862a | 194 | int err = 0, k; |
1001ff7a | 195 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
bf60862a AB |
196 | |
197 | for (k = 1; k < 33; ++k) { | |
b9da8bae | 198 | if (addr + (subpgsize * k) > (loff_t)(ebnum + 1) * mtd->erasesize) |
bf60862a | 199 | break; |
a312b78b | 200 | prandom_bytes_state(&rnd_state, writebuf, subpgsize * k); |
bf60862a | 201 | clear_data(readbuf, subpgsize * k); |
329ad399 | 202 | err = mtd_read(mtd, addr, subpgsize * k, &read, readbuf); |
bf60862a | 203 | if (unlikely(err || read != subpgsize * k)) { |
d57f4054 | 204 | if (mtd_is_bitflip(err) && read == subpgsize * k) { |
cd66a2df | 205 | pr_info("ECC correction at %#llx\n", |
bf60862a AB |
206 | (long long)addr); |
207 | err = 0; | |
208 | } else { | |
cd66a2df | 209 | pr_err("error: read failed at " |
bf60862a AB |
210 | "%#llx\n", (long long)addr); |
211 | return err ? err : -1; | |
212 | } | |
213 | } | |
214 | if (unlikely(memcmp(readbuf, writebuf, subpgsize * k))) { | |
cd66a2df | 215 | pr_err("error: verify failed at %#llx\n", |
bf60862a AB |
216 | (long long)addr); |
217 | errcnt += 1; | |
218 | } | |
219 | addr += subpgsize * k; | |
220 | } | |
221 | ||
222 | return err; | |
223 | } | |
224 | ||
225 | static int verify_eraseblock_ff(int ebnum) | |
226 | { | |
227 | uint32_t j; | |
30fa9848 | 228 | size_t read; |
bf60862a | 229 | int err = 0; |
1001ff7a | 230 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
bf60862a AB |
231 | |
232 | memset(writebuf, 0xff, subpgsize); | |
233 | for (j = 0; j < mtd->erasesize / subpgsize; ++j) { | |
234 | clear_data(readbuf, subpgsize); | |
329ad399 | 235 | err = mtd_read(mtd, addr, subpgsize, &read, readbuf); |
bf60862a | 236 | if (unlikely(err || read != subpgsize)) { |
d57f4054 | 237 | if (mtd_is_bitflip(err) && read == subpgsize) { |
cd66a2df | 238 | pr_info("ECC correction at %#llx\n", |
bf60862a AB |
239 | (long long)addr); |
240 | err = 0; | |
241 | } else { | |
cd66a2df | 242 | pr_err("error: read failed at " |
bf60862a AB |
243 | "%#llx\n", (long long)addr); |
244 | return err ? err : -1; | |
245 | } | |
246 | } | |
247 | if (unlikely(memcmp(readbuf, writebuf, subpgsize))) { | |
cd66a2df | 248 | pr_err("error: verify 0xff failed at " |
bf60862a AB |
249 | "%#llx\n", (long long)addr); |
250 | errcnt += 1; | |
251 | } | |
252 | addr += subpgsize; | |
253 | } | |
254 | ||
255 | return err; | |
256 | } | |
257 | ||
258 | static int verify_all_eraseblocks_ff(void) | |
259 | { | |
260 | int err; | |
261 | unsigned int i; | |
262 | ||
cd66a2df | 263 | pr_info("verifying all eraseblocks for 0xff\n"); |
bf60862a AB |
264 | for (i = 0; i < ebcnt; ++i) { |
265 | if (bbt[i]) | |
266 | continue; | |
267 | err = verify_eraseblock_ff(i); | |
268 | if (err) | |
269 | return err; | |
270 | if (i % 256 == 0) | |
cd66a2df | 271 | pr_info("verified up to eraseblock %u\n", i); |
2a6a28e7 RW |
272 | |
273 | err = mtdtest_relax(); | |
274 | if (err) | |
275 | return err; | |
bf60862a | 276 | } |
cd66a2df | 277 | pr_info("verified %u eraseblocks\n", i); |
bf60862a AB |
278 | return 0; |
279 | } | |
280 | ||
bf60862a AB |
281 | static int __init mtd_subpagetest_init(void) |
282 | { | |
283 | int err = 0; | |
284 | uint32_t i; | |
285 | uint64_t tmp; | |
286 | ||
287 | printk(KERN_INFO "\n"); | |
288 | printk(KERN_INFO "=================================================\n"); | |
7406060e WS |
289 | |
290 | if (dev < 0) { | |
064a7694 | 291 | pr_info("Please specify a valid mtd-device via module parameter\n"); |
cd66a2df | 292 | pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); |
7406060e WS |
293 | return -EINVAL; |
294 | } | |
295 | ||
cd66a2df | 296 | pr_info("MTD device: %d\n", dev); |
bf60862a AB |
297 | |
298 | mtd = get_mtd_device(NULL, dev); | |
299 | if (IS_ERR(mtd)) { | |
300 | err = PTR_ERR(mtd); | |
cd66a2df | 301 | pr_err("error: cannot get MTD device\n"); |
bf60862a AB |
302 | return err; |
303 | } | |
304 | ||
818b9739 | 305 | if (!mtd_type_is_nand(mtd)) { |
cd66a2df | 306 | pr_info("this test requires NAND flash\n"); |
bf60862a AB |
307 | goto out; |
308 | } | |
309 | ||
310 | subpgsize = mtd->writesize >> mtd->subpage_sft; | |
7b7e905e RT |
311 | tmp = mtd->size; |
312 | do_div(tmp, mtd->erasesize); | |
313 | ebcnt = tmp; | |
314 | pgcnt = mtd->erasesize / mtd->writesize; | |
315 | ||
cd66a2df | 316 | pr_info("MTD device size %llu, eraseblock size %u, " |
bf60862a AB |
317 | "page size %u, subpage size %u, count of eraseblocks %u, " |
318 | "pages per eraseblock %u, OOB size %u\n", | |
319 | (unsigned long long)mtd->size, mtd->erasesize, | |
320 | mtd->writesize, subpgsize, ebcnt, pgcnt, mtd->oobsize); | |
321 | ||
322 | err = -ENOMEM; | |
323 | bufsize = subpgsize * 32; | |
324 | writebuf = kmalloc(bufsize, GFP_KERNEL); | |
33777e66 | 325 | if (!writebuf) |
bf60862a | 326 | goto out; |
bf60862a | 327 | readbuf = kmalloc(bufsize, GFP_KERNEL); |
33777e66 | 328 | if (!readbuf) |
bf60862a | 329 | goto out; |
725cd71c AM |
330 | bbt = kzalloc(ebcnt, GFP_KERNEL); |
331 | if (!bbt) | |
332 | goto out; | |
bf60862a | 333 | |
725cd71c | 334 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); |
bf60862a AB |
335 | if (err) |
336 | goto out; | |
337 | ||
725cd71c | 338 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); |
bf60862a AB |
339 | if (err) |
340 | goto out; | |
341 | ||
cd66a2df | 342 | pr_info("writing whole device\n"); |
a312b78b | 343 | prandom_seed_state(&rnd_state, 1); |
bf60862a AB |
344 | for (i = 0; i < ebcnt; ++i) { |
345 | if (bbt[i]) | |
346 | continue; | |
347 | err = write_eraseblock(i); | |
348 | if (unlikely(err)) | |
349 | goto out; | |
350 | if (i % 256 == 0) | |
cd66a2df | 351 | pr_info("written up to eraseblock %u\n", i); |
2a6a28e7 RW |
352 | |
353 | err = mtdtest_relax(); | |
354 | if (err) | |
355 | goto out; | |
bf60862a | 356 | } |
cd66a2df | 357 | pr_info("written %u eraseblocks\n", i); |
bf60862a | 358 | |
a312b78b | 359 | prandom_seed_state(&rnd_state, 1); |
cd66a2df | 360 | pr_info("verifying all eraseblocks\n"); |
bf60862a AB |
361 | for (i = 0; i < ebcnt; ++i) { |
362 | if (bbt[i]) | |
363 | continue; | |
364 | err = verify_eraseblock(i); | |
365 | if (unlikely(err)) | |
366 | goto out; | |
367 | if (i % 256 == 0) | |
cd66a2df | 368 | pr_info("verified up to eraseblock %u\n", i); |
2a6a28e7 RW |
369 | |
370 | err = mtdtest_relax(); | |
371 | if (err) | |
372 | goto out; | |
bf60862a | 373 | } |
cd66a2df | 374 | pr_info("verified %u eraseblocks\n", i); |
bf60862a | 375 | |
725cd71c | 376 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); |
bf60862a AB |
377 | if (err) |
378 | goto out; | |
379 | ||
380 | err = verify_all_eraseblocks_ff(); | |
381 | if (err) | |
382 | goto out; | |
383 | ||
384 | /* Write all eraseblocks */ | |
a312b78b | 385 | prandom_seed_state(&rnd_state, 3); |
cd66a2df | 386 | pr_info("writing whole device\n"); |
bf60862a AB |
387 | for (i = 0; i < ebcnt; ++i) { |
388 | if (bbt[i]) | |
389 | continue; | |
390 | err = write_eraseblock2(i); | |
391 | if (unlikely(err)) | |
392 | goto out; | |
393 | if (i % 256 == 0) | |
cd66a2df | 394 | pr_info("written up to eraseblock %u\n", i); |
2a6a28e7 RW |
395 | |
396 | err = mtdtest_relax(); | |
397 | if (err) | |
398 | goto out; | |
bf60862a | 399 | } |
cd66a2df | 400 | pr_info("written %u eraseblocks\n", i); |
bf60862a AB |
401 | |
402 | /* Check all eraseblocks */ | |
a312b78b | 403 | prandom_seed_state(&rnd_state, 3); |
cd66a2df | 404 | pr_info("verifying all eraseblocks\n"); |
bf60862a AB |
405 | for (i = 0; i < ebcnt; ++i) { |
406 | if (bbt[i]) | |
407 | continue; | |
408 | err = verify_eraseblock2(i); | |
409 | if (unlikely(err)) | |
410 | goto out; | |
411 | if (i % 256 == 0) | |
cd66a2df | 412 | pr_info("verified up to eraseblock %u\n", i); |
2a6a28e7 RW |
413 | |
414 | err = mtdtest_relax(); | |
415 | if (err) | |
416 | goto out; | |
bf60862a | 417 | } |
cd66a2df | 418 | pr_info("verified %u eraseblocks\n", i); |
bf60862a | 419 | |
725cd71c | 420 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); |
bf60862a AB |
421 | if (err) |
422 | goto out; | |
423 | ||
424 | err = verify_all_eraseblocks_ff(); | |
425 | if (err) | |
426 | goto out; | |
427 | ||
cd66a2df | 428 | pr_info("finished with %d errors\n", errcnt); |
bf60862a AB |
429 | |
430 | out: | |
431 | kfree(bbt); | |
432 | kfree(readbuf); | |
433 | kfree(writebuf); | |
434 | put_mtd_device(mtd); | |
435 | if (err) | |
cd66a2df | 436 | pr_info("error %d occurred\n", err); |
bf60862a AB |
437 | printk(KERN_INFO "=================================================\n"); |
438 | return err; | |
439 | } | |
440 | module_init(mtd_subpagetest_init); | |
441 | ||
442 | static void __exit mtd_subpagetest_exit(void) | |
443 | { | |
444 | return; | |
445 | } | |
446 | module_exit(mtd_subpagetest_exit); | |
447 | ||
448 | MODULE_DESCRIPTION("Subpage test module"); | |
449 | MODULE_AUTHOR("Adrian Hunter"); | |
450 | MODULE_LICENSE("GPL"); |