Commit | Line | Data |
---|---|---|
e73f2174 AB |
1 | /* |
2 | * Copyright (C) 2006-2008 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 page read and write on MTD device. | |
18 | * | |
19 | * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> | |
20 | */ | |
21 | ||
bb998419 VN |
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
23 | ||
e73f2174 AB |
24 | #include <asm/div64.h> |
25 | #include <linux/init.h> | |
26 | #include <linux/module.h> | |
27 | #include <linux/moduleparam.h> | |
28 | #include <linux/err.h> | |
29 | #include <linux/mtd/mtd.h> | |
5a0e3ad6 | 30 | #include <linux/slab.h> |
e73f2174 | 31 | #include <linux/sched.h> |
825b8ccb | 32 | #include <linux/random.h> |
e73f2174 | 33 | |
66b28183 AM |
34 | #include "mtd_test.h" |
35 | ||
7406060e | 36 | static int dev = -EINVAL; |
e73f2174 AB |
37 | module_param(dev, int, S_IRUGO); |
38 | MODULE_PARM_DESC(dev, "MTD device number to use"); | |
39 | ||
40 | static struct mtd_info *mtd; | |
41 | static unsigned char *twopages; | |
42 | static unsigned char *writebuf; | |
43 | static unsigned char *boundary; | |
44 | static unsigned char *bbt; | |
45 | ||
46 | static int pgsize; | |
47 | static int bufsize; | |
48 | static int ebcnt; | |
49 | static int pgcnt; | |
50 | static int errcnt; | |
825b8ccb | 51 | static struct rnd_state rnd_state; |
e73f2174 | 52 | |
e73f2174 AB |
53 | static int write_eraseblock(int ebnum) |
54 | { | |
e73f2174 AB |
55 | loff_t addr = ebnum * mtd->erasesize; |
56 | ||
825b8ccb | 57 | prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize); |
e73f2174 | 58 | cond_resched(); |
8a9f4aa3 | 59 | return mtdtest_write(mtd, addr, mtd->erasesize, writebuf); |
e73f2174 AB |
60 | } |
61 | ||
62 | static int verify_eraseblock(int ebnum) | |
63 | { | |
64 | uint32_t j; | |
e73f2174 AB |
65 | int err = 0, i; |
66 | loff_t addr0, addrn; | |
67 | loff_t addr = ebnum * mtd->erasesize; | |
68 | ||
69 | addr0 = 0; | |
c6f7e7be | 70 | for (i = 0; i < ebcnt && bbt[i]; ++i) |
e73f2174 AB |
71 | addr0 += mtd->erasesize; |
72 | ||
73 | addrn = mtd->size; | |
c6f7e7be | 74 | for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i) |
e73f2174 AB |
75 | addrn -= mtd->erasesize; |
76 | ||
825b8ccb | 77 | prandom_bytes_state(&rnd_state, writebuf, mtd->erasesize); |
e73f2174 AB |
78 | for (j = 0; j < pgcnt - 1; ++j, addr += pgsize) { |
79 | /* Do a read to set the internal dataRAMs to different data */ | |
66b28183 | 80 | err = mtdtest_read(mtd, addr0, bufsize, twopages); |
abc173ad | 81 | if (err) |
e73f2174 | 82 | return err; |
66b28183 | 83 | err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages); |
abc173ad | 84 | if (err) |
e73f2174 | 85 | return err; |
e73f2174 | 86 | memset(twopages, 0, bufsize); |
66b28183 | 87 | err = mtdtest_read(mtd, addr, bufsize, twopages); |
abc173ad | 88 | if (err) |
e73f2174 | 89 | break; |
e73f2174 | 90 | if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) { |
bb998419 | 91 | pr_err("error: verify failed at %#llx\n", |
e73f2174 AB |
92 | (long long)addr); |
93 | errcnt += 1; | |
94 | } | |
95 | } | |
96 | /* Check boundary between eraseblocks */ | |
97 | if (addr <= addrn - pgsize - pgsize && !bbt[ebnum + 1]) { | |
825b8ccb AM |
98 | struct rnd_state old_state = rnd_state; |
99 | ||
e73f2174 | 100 | /* Do a read to set the internal dataRAMs to different data */ |
66b28183 | 101 | err = mtdtest_read(mtd, addr0, bufsize, twopages); |
abc173ad | 102 | if (err) |
e73f2174 | 103 | return err; |
66b28183 | 104 | err = mtdtest_read(mtd, addrn - bufsize, bufsize, twopages); |
abc173ad | 105 | if (err) |
e73f2174 | 106 | return err; |
e73f2174 | 107 | memset(twopages, 0, bufsize); |
66b28183 | 108 | err = mtdtest_read(mtd, addr, bufsize, twopages); |
abc173ad | 109 | if (err) |
e73f2174 | 110 | return err; |
e73f2174 | 111 | memcpy(boundary, writebuf + mtd->erasesize - pgsize, pgsize); |
825b8ccb | 112 | prandom_bytes_state(&rnd_state, boundary + pgsize, pgsize); |
e73f2174 | 113 | if (memcmp(twopages, boundary, bufsize)) { |
bb998419 | 114 | pr_err("error: verify failed at %#llx\n", |
e73f2174 AB |
115 | (long long)addr); |
116 | errcnt += 1; | |
117 | } | |
825b8ccb | 118 | rnd_state = old_state; |
e73f2174 AB |
119 | } |
120 | return err; | |
121 | } | |
122 | ||
123 | static int crosstest(void) | |
124 | { | |
e73f2174 AB |
125 | int err = 0, i; |
126 | loff_t addr, addr0, addrn; | |
127 | unsigned char *pp1, *pp2, *pp3, *pp4; | |
128 | ||
bb998419 | 129 | pr_info("crosstest\n"); |
e73f2174 | 130 | pp1 = kmalloc(pgsize * 4, GFP_KERNEL); |
33777e66 | 131 | if (!pp1) |
e73f2174 | 132 | return -ENOMEM; |
e73f2174 AB |
133 | pp2 = pp1 + pgsize; |
134 | pp3 = pp2 + pgsize; | |
135 | pp4 = pp3 + pgsize; | |
136 | memset(pp1, 0, pgsize * 4); | |
137 | ||
138 | addr0 = 0; | |
c6f7e7be | 139 | for (i = 0; i < ebcnt && bbt[i]; ++i) |
e73f2174 AB |
140 | addr0 += mtd->erasesize; |
141 | ||
142 | addrn = mtd->size; | |
c6f7e7be | 143 | for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i) |
e73f2174 AB |
144 | addrn -= mtd->erasesize; |
145 | ||
146 | /* Read 2nd-to-last page to pp1 */ | |
e73f2174 | 147 | addr = addrn - pgsize - pgsize; |
66b28183 AM |
148 | err = mtdtest_read(mtd, addr, pgsize, pp1); |
149 | if (err) { | |
e73f2174 AB |
150 | kfree(pp1); |
151 | return err; | |
152 | } | |
153 | ||
154 | /* Read 3rd-to-last page to pp1 */ | |
e73f2174 | 155 | addr = addrn - pgsize - pgsize - pgsize; |
66b28183 AM |
156 | err = mtdtest_read(mtd, addr, pgsize, pp1); |
157 | if (err) { | |
e73f2174 AB |
158 | kfree(pp1); |
159 | return err; | |
160 | } | |
161 | ||
162 | /* Read first page to pp2 */ | |
e73f2174 | 163 | addr = addr0; |
bb998419 | 164 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
165 | err = mtdtest_read(mtd, addr, pgsize, pp2); |
166 | if (err) { | |
e73f2174 AB |
167 | kfree(pp1); |
168 | return err; | |
169 | } | |
170 | ||
171 | /* Read last page to pp3 */ | |
e73f2174 | 172 | addr = addrn - pgsize; |
bb998419 | 173 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
174 | err = mtdtest_read(mtd, addr, pgsize, pp3); |
175 | if (err) { | |
e73f2174 AB |
176 | kfree(pp1); |
177 | return err; | |
178 | } | |
179 | ||
180 | /* Read first page again to pp4 */ | |
e73f2174 | 181 | addr = addr0; |
bb998419 | 182 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
183 | err = mtdtest_read(mtd, addr, pgsize, pp4); |
184 | if (err) { | |
e73f2174 AB |
185 | kfree(pp1); |
186 | return err; | |
187 | } | |
188 | ||
189 | /* pp2 and pp4 should be the same */ | |
bb998419 | 190 | pr_info("verifying pages read at %#llx match\n", |
e73f2174 AB |
191 | (long long)addr0); |
192 | if (memcmp(pp2, pp4, pgsize)) { | |
bb998419 | 193 | pr_err("verify failed!\n"); |
e73f2174 AB |
194 | errcnt += 1; |
195 | } else if (!err) | |
bb998419 | 196 | pr_info("crosstest ok\n"); |
e73f2174 AB |
197 | kfree(pp1); |
198 | return err; | |
199 | } | |
200 | ||
201 | static int erasecrosstest(void) | |
202 | { | |
7fc14bce | 203 | int err = 0, i, ebnum, ebnum2; |
e73f2174 AB |
204 | loff_t addr0; |
205 | char *readbuf = twopages; | |
206 | ||
bb998419 | 207 | pr_info("erasecrosstest\n"); |
e73f2174 AB |
208 | |
209 | ebnum = 0; | |
210 | addr0 = 0; | |
c6f7e7be | 211 | for (i = 0; i < ebcnt && bbt[i]; ++i) { |
e73f2174 AB |
212 | addr0 += mtd->erasesize; |
213 | ebnum += 1; | |
214 | } | |
215 | ||
216 | ebnum2 = ebcnt - 1; | |
217 | while (ebnum2 && bbt[ebnum2]) | |
218 | ebnum2 -= 1; | |
219 | ||
bb998419 | 220 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 221 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
222 | if (err) |
223 | return err; | |
224 | ||
bb998419 | 225 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 226 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
e73f2174 | 227 | strcpy(writebuf, "There is no data like this!"); |
66b28183 | 228 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 229 | if (err) |
66b28183 | 230 | return err; |
e73f2174 | 231 | |
bb998419 | 232 | pr_info("reading 1st page of block %d\n", ebnum); |
e73f2174 | 233 | memset(readbuf, 0, pgsize); |
66b28183 | 234 | err = mtdtest_read(mtd, addr0, pgsize, readbuf); |
abc173ad | 235 | if (err) |
66b28183 | 236 | return err; |
e73f2174 | 237 | |
bb998419 | 238 | pr_info("verifying 1st page of block %d\n", ebnum); |
e73f2174 | 239 | if (memcmp(writebuf, readbuf, pgsize)) { |
bb998419 | 240 | pr_err("verify failed!\n"); |
e73f2174 | 241 | errcnt += 1; |
7fc14bce | 242 | return -1; |
e73f2174 AB |
243 | } |
244 | ||
bb998419 | 245 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 246 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
247 | if (err) |
248 | return err; | |
249 | ||
bb998419 | 250 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 251 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
e73f2174 | 252 | strcpy(writebuf, "There is no data like this!"); |
66b28183 | 253 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 254 | if (err) |
66b28183 | 255 | return err; |
e73f2174 | 256 | |
bb998419 | 257 | pr_info("erasing block %d\n", ebnum2); |
66b28183 | 258 | err = mtdtest_erase_eraseblock(mtd, ebnum2); |
e73f2174 AB |
259 | if (err) |
260 | return err; | |
261 | ||
bb998419 | 262 | pr_info("reading 1st page of block %d\n", ebnum); |
e73f2174 | 263 | memset(readbuf, 0, pgsize); |
66b28183 | 264 | err = mtdtest_read(mtd, addr0, pgsize, readbuf); |
abc173ad | 265 | if (err) |
66b28183 | 266 | return err; |
e73f2174 | 267 | |
bb998419 | 268 | pr_info("verifying 1st page of block %d\n", ebnum); |
e73f2174 | 269 | if (memcmp(writebuf, readbuf, pgsize)) { |
bb998419 | 270 | pr_err("verify failed!\n"); |
e73f2174 | 271 | errcnt += 1; |
7fc14bce | 272 | return -1; |
e73f2174 AB |
273 | } |
274 | ||
7fc14bce | 275 | if (!err) |
bb998419 | 276 | pr_info("erasecrosstest ok\n"); |
e73f2174 AB |
277 | return err; |
278 | } | |
279 | ||
280 | static int erasetest(void) | |
281 | { | |
e73f2174 AB |
282 | int err = 0, i, ebnum, ok = 1; |
283 | loff_t addr0; | |
284 | ||
bb998419 | 285 | pr_info("erasetest\n"); |
e73f2174 AB |
286 | |
287 | ebnum = 0; | |
288 | addr0 = 0; | |
c6f7e7be | 289 | for (i = 0; i < ebcnt && bbt[i]; ++i) { |
e73f2174 AB |
290 | addr0 += mtd->erasesize; |
291 | ebnum += 1; | |
292 | } | |
293 | ||
bb998419 | 294 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 295 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
296 | if (err) |
297 | return err; | |
298 | ||
bb998419 | 299 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 300 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
66b28183 | 301 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 302 | if (err) |
66b28183 | 303 | return err; |
e73f2174 | 304 | |
bb998419 | 305 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 306 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
307 | if (err) |
308 | return err; | |
309 | ||
bb998419 | 310 | pr_info("reading 1st page of block %d\n", ebnum); |
66b28183 | 311 | err = mtdtest_read(mtd, addr0, pgsize, twopages); |
abc173ad | 312 | if (err) |
66b28183 | 313 | return err; |
e73f2174 | 314 | |
bb998419 | 315 | pr_info("verifying 1st page of block %d is all 0xff\n", |
e73f2174 AB |
316 | ebnum); |
317 | for (i = 0; i < pgsize; ++i) | |
318 | if (twopages[i] != 0xff) { | |
bb998419 | 319 | pr_err("verifying all 0xff failed at %d\n", |
e73f2174 AB |
320 | i); |
321 | errcnt += 1; | |
322 | ok = 0; | |
323 | break; | |
324 | } | |
325 | ||
326 | if (ok && !err) | |
bb998419 | 327 | pr_info("erasetest ok\n"); |
e73f2174 AB |
328 | |
329 | return err; | |
330 | } | |
331 | ||
e73f2174 AB |
332 | static int __init mtd_pagetest_init(void) |
333 | { | |
334 | int err = 0; | |
335 | uint64_t tmp; | |
336 | uint32_t i; | |
337 | ||
338 | printk(KERN_INFO "\n"); | |
339 | printk(KERN_INFO "=================================================\n"); | |
7406060e WS |
340 | |
341 | if (dev < 0) { | |
064a7694 | 342 | pr_info("Please specify a valid mtd-device via module parameter\n"); |
bb998419 | 343 | pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); |
7406060e WS |
344 | return -EINVAL; |
345 | } | |
346 | ||
bb998419 | 347 | pr_info("MTD device: %d\n", dev); |
e73f2174 AB |
348 | |
349 | mtd = get_mtd_device(NULL, dev); | |
350 | if (IS_ERR(mtd)) { | |
351 | err = PTR_ERR(mtd); | |
bb998419 | 352 | pr_err("error: cannot get MTD device\n"); |
e73f2174 AB |
353 | return err; |
354 | } | |
355 | ||
356 | if (mtd->type != MTD_NANDFLASH) { | |
bb998419 | 357 | pr_info("this test requires NAND flash\n"); |
e73f2174 AB |
358 | goto out; |
359 | } | |
360 | ||
361 | tmp = mtd->size; | |
362 | do_div(tmp, mtd->erasesize); | |
363 | ebcnt = tmp; | |
364 | pgcnt = mtd->erasesize / mtd->writesize; | |
4c2b8a62 | 365 | pgsize = mtd->writesize; |
e73f2174 | 366 | |
bb998419 | 367 | pr_info("MTD device size %llu, eraseblock size %u, " |
e73f2174 AB |
368 | "page size %u, count of eraseblocks %u, pages per " |
369 | "eraseblock %u, OOB size %u\n", | |
370 | (unsigned long long)mtd->size, mtd->erasesize, | |
371 | pgsize, ebcnt, pgcnt, mtd->oobsize); | |
372 | ||
373 | err = -ENOMEM; | |
374 | bufsize = pgsize * 2; | |
375 | writebuf = kmalloc(mtd->erasesize, GFP_KERNEL); | |
33777e66 | 376 | if (!writebuf) |
e73f2174 | 377 | goto out; |
e73f2174 | 378 | twopages = kmalloc(bufsize, GFP_KERNEL); |
33777e66 | 379 | if (!twopages) |
e73f2174 | 380 | goto out; |
e73f2174 | 381 | boundary = kmalloc(bufsize, GFP_KERNEL); |
33777e66 | 382 | if (!boundary) |
e73f2174 | 383 | goto out; |
e73f2174 | 384 | |
66b28183 AM |
385 | bbt = kzalloc(ebcnt, GFP_KERNEL); |
386 | if (!bbt) | |
387 | goto out; | |
388 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); | |
e73f2174 AB |
389 | if (err) |
390 | goto out; | |
391 | ||
392 | /* Erase all eraseblocks */ | |
bb998419 | 393 | pr_info("erasing whole device\n"); |
66b28183 AM |
394 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); |
395 | if (err) | |
396 | goto out; | |
397 | pr_info("erased %u eraseblocks\n", ebcnt); | |
e73f2174 AB |
398 | |
399 | /* Write all eraseblocks */ | |
825b8ccb | 400 | prandom_seed_state(&rnd_state, 1); |
bb998419 | 401 | pr_info("writing whole device\n"); |
e73f2174 AB |
402 | for (i = 0; i < ebcnt; ++i) { |
403 | if (bbt[i]) | |
404 | continue; | |
405 | err = write_eraseblock(i); | |
406 | if (err) | |
407 | goto out; | |
408 | if (i % 256 == 0) | |
bb998419 | 409 | pr_info("written up to eraseblock %u\n", i); |
e73f2174 AB |
410 | cond_resched(); |
411 | } | |
bb998419 | 412 | pr_info("written %u eraseblocks\n", i); |
e73f2174 AB |
413 | |
414 | /* Check all eraseblocks */ | |
825b8ccb | 415 | prandom_seed_state(&rnd_state, 1); |
bb998419 | 416 | pr_info("verifying all eraseblocks\n"); |
e73f2174 AB |
417 | for (i = 0; i < ebcnt; ++i) { |
418 | if (bbt[i]) | |
419 | continue; | |
420 | err = verify_eraseblock(i); | |
421 | if (err) | |
422 | goto out; | |
423 | if (i % 256 == 0) | |
bb998419 | 424 | pr_info("verified up to eraseblock %u\n", i); |
e73f2174 AB |
425 | cond_resched(); |
426 | } | |
bb998419 | 427 | pr_info("verified %u eraseblocks\n", i); |
e73f2174 AB |
428 | |
429 | err = crosstest(); | |
430 | if (err) | |
431 | goto out; | |
432 | ||
433 | err = erasecrosstest(); | |
434 | if (err) | |
435 | goto out; | |
436 | ||
437 | err = erasetest(); | |
438 | if (err) | |
439 | goto out; | |
440 | ||
bb998419 | 441 | pr_info("finished with %d errors\n", errcnt); |
e73f2174 AB |
442 | out: |
443 | ||
444 | kfree(bbt); | |
445 | kfree(boundary); | |
446 | kfree(twopages); | |
447 | kfree(writebuf); | |
448 | put_mtd_device(mtd); | |
449 | if (err) | |
bb998419 | 450 | pr_info("error %d occurred\n", err); |
e73f2174 AB |
451 | printk(KERN_INFO "=================================================\n"); |
452 | return err; | |
453 | } | |
454 | module_init(mtd_pagetest_init); | |
455 | ||
456 | static void __exit mtd_pagetest_exit(void) | |
457 | { | |
458 | return; | |
459 | } | |
460 | module_exit(mtd_pagetest_exit); | |
461 | ||
462 | MODULE_DESCRIPTION("NAND page test"); | |
463 | MODULE_AUTHOR("Adrian Hunter"); | |
464 | MODULE_LICENSE("GPL"); |