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 | { | |
1001ff7a | 55 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
e73f2174 | 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; | |
1001ff7a | 67 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
e73f2174 AB |
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"); |
6e75632a | 130 | pp1 = kzalloc(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; | |
e73f2174 AB |
136 | |
137 | addr0 = 0; | |
c6f7e7be | 138 | for (i = 0; i < ebcnt && bbt[i]; ++i) |
e73f2174 AB |
139 | addr0 += mtd->erasesize; |
140 | ||
141 | addrn = mtd->size; | |
c6f7e7be | 142 | for (i = 0; i < ebcnt && bbt[ebcnt - i - 1]; ++i) |
e73f2174 AB |
143 | addrn -= mtd->erasesize; |
144 | ||
145 | /* Read 2nd-to-last page to pp1 */ | |
e73f2174 | 146 | addr = addrn - pgsize - pgsize; |
66b28183 AM |
147 | err = mtdtest_read(mtd, addr, pgsize, pp1); |
148 | if (err) { | |
e73f2174 AB |
149 | kfree(pp1); |
150 | return err; | |
151 | } | |
152 | ||
153 | /* Read 3rd-to-last page to pp1 */ | |
e73f2174 | 154 | addr = addrn - pgsize - pgsize - pgsize; |
66b28183 AM |
155 | err = mtdtest_read(mtd, addr, pgsize, pp1); |
156 | if (err) { | |
e73f2174 AB |
157 | kfree(pp1); |
158 | return err; | |
159 | } | |
160 | ||
161 | /* Read first page to pp2 */ | |
e73f2174 | 162 | addr = addr0; |
bb998419 | 163 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
164 | err = mtdtest_read(mtd, addr, pgsize, pp2); |
165 | if (err) { | |
e73f2174 AB |
166 | kfree(pp1); |
167 | return err; | |
168 | } | |
169 | ||
170 | /* Read last page to pp3 */ | |
e73f2174 | 171 | addr = addrn - pgsize; |
bb998419 | 172 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
173 | err = mtdtest_read(mtd, addr, pgsize, pp3); |
174 | if (err) { | |
e73f2174 AB |
175 | kfree(pp1); |
176 | return err; | |
177 | } | |
178 | ||
179 | /* Read first page again to pp4 */ | |
e73f2174 | 180 | addr = addr0; |
bb998419 | 181 | pr_info("reading page at %#llx\n", (long long)addr); |
66b28183 AM |
182 | err = mtdtest_read(mtd, addr, pgsize, pp4); |
183 | if (err) { | |
e73f2174 AB |
184 | kfree(pp1); |
185 | return err; | |
186 | } | |
187 | ||
188 | /* pp2 and pp4 should be the same */ | |
bb998419 | 189 | pr_info("verifying pages read at %#llx match\n", |
e73f2174 AB |
190 | (long long)addr0); |
191 | if (memcmp(pp2, pp4, pgsize)) { | |
bb998419 | 192 | pr_err("verify failed!\n"); |
e73f2174 AB |
193 | errcnt += 1; |
194 | } else if (!err) | |
bb998419 | 195 | pr_info("crosstest ok\n"); |
e73f2174 AB |
196 | kfree(pp1); |
197 | return err; | |
198 | } | |
199 | ||
200 | static int erasecrosstest(void) | |
201 | { | |
7fc14bce | 202 | int err = 0, i, ebnum, ebnum2; |
e73f2174 AB |
203 | loff_t addr0; |
204 | char *readbuf = twopages; | |
205 | ||
bb998419 | 206 | pr_info("erasecrosstest\n"); |
e73f2174 AB |
207 | |
208 | ebnum = 0; | |
209 | addr0 = 0; | |
c6f7e7be | 210 | for (i = 0; i < ebcnt && bbt[i]; ++i) { |
e73f2174 AB |
211 | addr0 += mtd->erasesize; |
212 | ebnum += 1; | |
213 | } | |
214 | ||
215 | ebnum2 = ebcnt - 1; | |
216 | while (ebnum2 && bbt[ebnum2]) | |
217 | ebnum2 -= 1; | |
218 | ||
bb998419 | 219 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 220 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
221 | if (err) |
222 | return err; | |
223 | ||
bb998419 | 224 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 225 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
e73f2174 | 226 | strcpy(writebuf, "There is no data like this!"); |
66b28183 | 227 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 228 | if (err) |
66b28183 | 229 | return err; |
e73f2174 | 230 | |
bb998419 | 231 | pr_info("reading 1st page of block %d\n", ebnum); |
e73f2174 | 232 | memset(readbuf, 0, pgsize); |
66b28183 | 233 | err = mtdtest_read(mtd, addr0, pgsize, readbuf); |
abc173ad | 234 | if (err) |
66b28183 | 235 | return err; |
e73f2174 | 236 | |
bb998419 | 237 | pr_info("verifying 1st page of block %d\n", ebnum); |
e73f2174 | 238 | if (memcmp(writebuf, readbuf, pgsize)) { |
bb998419 | 239 | pr_err("verify failed!\n"); |
e73f2174 | 240 | errcnt += 1; |
7fc14bce | 241 | return -1; |
e73f2174 AB |
242 | } |
243 | ||
bb998419 | 244 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 245 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
246 | if (err) |
247 | return err; | |
248 | ||
bb998419 | 249 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 250 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
e73f2174 | 251 | strcpy(writebuf, "There is no data like this!"); |
66b28183 | 252 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 253 | if (err) |
66b28183 | 254 | return err; |
e73f2174 | 255 | |
bb998419 | 256 | pr_info("erasing block %d\n", ebnum2); |
66b28183 | 257 | err = mtdtest_erase_eraseblock(mtd, ebnum2); |
e73f2174 AB |
258 | if (err) |
259 | return err; | |
260 | ||
bb998419 | 261 | pr_info("reading 1st page of block %d\n", ebnum); |
e73f2174 | 262 | memset(readbuf, 0, pgsize); |
66b28183 | 263 | err = mtdtest_read(mtd, addr0, pgsize, readbuf); |
abc173ad | 264 | if (err) |
66b28183 | 265 | return err; |
e73f2174 | 266 | |
bb998419 | 267 | pr_info("verifying 1st page of block %d\n", ebnum); |
e73f2174 | 268 | if (memcmp(writebuf, readbuf, pgsize)) { |
bb998419 | 269 | pr_err("verify failed!\n"); |
e73f2174 | 270 | errcnt += 1; |
7fc14bce | 271 | return -1; |
e73f2174 AB |
272 | } |
273 | ||
7fc14bce | 274 | if (!err) |
bb998419 | 275 | pr_info("erasecrosstest ok\n"); |
e73f2174 AB |
276 | return err; |
277 | } | |
278 | ||
279 | static int erasetest(void) | |
280 | { | |
e73f2174 AB |
281 | int err = 0, i, ebnum, ok = 1; |
282 | loff_t addr0; | |
283 | ||
bb998419 | 284 | pr_info("erasetest\n"); |
e73f2174 AB |
285 | |
286 | ebnum = 0; | |
287 | addr0 = 0; | |
c6f7e7be | 288 | for (i = 0; i < ebcnt && bbt[i]; ++i) { |
e73f2174 AB |
289 | addr0 += mtd->erasesize; |
290 | ebnum += 1; | |
291 | } | |
292 | ||
bb998419 | 293 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 294 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
295 | if (err) |
296 | return err; | |
297 | ||
bb998419 | 298 | pr_info("writing 1st page of block %d\n", ebnum); |
825b8ccb | 299 | prandom_bytes_state(&rnd_state, writebuf, pgsize); |
66b28183 | 300 | err = mtdtest_write(mtd, addr0, pgsize, writebuf); |
8a9f4aa3 | 301 | if (err) |
66b28183 | 302 | return err; |
e73f2174 | 303 | |
bb998419 | 304 | pr_info("erasing block %d\n", ebnum); |
66b28183 | 305 | err = mtdtest_erase_eraseblock(mtd, ebnum); |
e73f2174 AB |
306 | if (err) |
307 | return err; | |
308 | ||
bb998419 | 309 | pr_info("reading 1st page of block %d\n", ebnum); |
66b28183 | 310 | err = mtdtest_read(mtd, addr0, pgsize, twopages); |
abc173ad | 311 | if (err) |
66b28183 | 312 | return err; |
e73f2174 | 313 | |
bb998419 | 314 | pr_info("verifying 1st page of block %d is all 0xff\n", |
e73f2174 AB |
315 | ebnum); |
316 | for (i = 0; i < pgsize; ++i) | |
317 | if (twopages[i] != 0xff) { | |
bb998419 | 318 | pr_err("verifying all 0xff failed at %d\n", |
e73f2174 AB |
319 | i); |
320 | errcnt += 1; | |
321 | ok = 0; | |
322 | break; | |
323 | } | |
324 | ||
325 | if (ok && !err) | |
bb998419 | 326 | pr_info("erasetest ok\n"); |
e73f2174 AB |
327 | |
328 | return err; | |
329 | } | |
330 | ||
e73f2174 AB |
331 | static int __init mtd_pagetest_init(void) |
332 | { | |
333 | int err = 0; | |
334 | uint64_t tmp; | |
335 | uint32_t i; | |
336 | ||
337 | printk(KERN_INFO "\n"); | |
338 | printk(KERN_INFO "=================================================\n"); | |
7406060e WS |
339 | |
340 | if (dev < 0) { | |
064a7694 | 341 | pr_info("Please specify a valid mtd-device via module parameter\n"); |
bb998419 | 342 | pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n"); |
7406060e WS |
343 | return -EINVAL; |
344 | } | |
345 | ||
bb998419 | 346 | pr_info("MTD device: %d\n", dev); |
e73f2174 AB |
347 | |
348 | mtd = get_mtd_device(NULL, dev); | |
349 | if (IS_ERR(mtd)) { | |
350 | err = PTR_ERR(mtd); | |
bb998419 | 351 | pr_err("error: cannot get MTD device\n"); |
e73f2174 AB |
352 | return err; |
353 | } | |
354 | ||
818b9739 | 355 | if (!mtd_type_is_nand(mtd)) { |
bb998419 | 356 | pr_info("this test requires NAND flash\n"); |
e73f2174 AB |
357 | goto out; |
358 | } | |
359 | ||
360 | tmp = mtd->size; | |
361 | do_div(tmp, mtd->erasesize); | |
362 | ebcnt = tmp; | |
363 | pgcnt = mtd->erasesize / mtd->writesize; | |
4c2b8a62 | 364 | pgsize = mtd->writesize; |
e73f2174 | 365 | |
bb998419 | 366 | pr_info("MTD device size %llu, eraseblock size %u, " |
e73f2174 AB |
367 | "page size %u, count of eraseblocks %u, pages per " |
368 | "eraseblock %u, OOB size %u\n", | |
369 | (unsigned long long)mtd->size, mtd->erasesize, | |
370 | pgsize, ebcnt, pgcnt, mtd->oobsize); | |
371 | ||
372 | err = -ENOMEM; | |
373 | bufsize = pgsize * 2; | |
374 | writebuf = kmalloc(mtd->erasesize, GFP_KERNEL); | |
33777e66 | 375 | if (!writebuf) |
e73f2174 | 376 | goto out; |
e73f2174 | 377 | twopages = kmalloc(bufsize, GFP_KERNEL); |
33777e66 | 378 | if (!twopages) |
e73f2174 | 379 | goto out; |
e73f2174 | 380 | boundary = kmalloc(bufsize, GFP_KERNEL); |
33777e66 | 381 | if (!boundary) |
e73f2174 | 382 | goto out; |
e73f2174 | 383 | |
66b28183 AM |
384 | bbt = kzalloc(ebcnt, GFP_KERNEL); |
385 | if (!bbt) | |
386 | goto out; | |
387 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, 0, ebcnt); | |
e73f2174 AB |
388 | if (err) |
389 | goto out; | |
390 | ||
391 | /* Erase all eraseblocks */ | |
bb998419 | 392 | pr_info("erasing whole device\n"); |
66b28183 AM |
393 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, 0, ebcnt); |
394 | if (err) | |
395 | goto out; | |
396 | pr_info("erased %u eraseblocks\n", ebcnt); | |
e73f2174 AB |
397 | |
398 | /* Write all eraseblocks */ | |
825b8ccb | 399 | prandom_seed_state(&rnd_state, 1); |
bb998419 | 400 | pr_info("writing whole device\n"); |
e73f2174 AB |
401 | for (i = 0; i < ebcnt; ++i) { |
402 | if (bbt[i]) | |
403 | continue; | |
404 | err = write_eraseblock(i); | |
405 | if (err) | |
406 | goto out; | |
407 | if (i % 256 == 0) | |
bb998419 | 408 | pr_info("written up to eraseblock %u\n", i); |
2a6a28e7 RW |
409 | |
410 | err = mtdtest_relax(); | |
411 | if (err) | |
412 | goto out; | |
e73f2174 | 413 | } |
bb998419 | 414 | pr_info("written %u eraseblocks\n", i); |
e73f2174 AB |
415 | |
416 | /* Check all eraseblocks */ | |
825b8ccb | 417 | prandom_seed_state(&rnd_state, 1); |
bb998419 | 418 | pr_info("verifying all eraseblocks\n"); |
e73f2174 AB |
419 | for (i = 0; i < ebcnt; ++i) { |
420 | if (bbt[i]) | |
421 | continue; | |
422 | err = verify_eraseblock(i); | |
423 | if (err) | |
424 | goto out; | |
425 | if (i % 256 == 0) | |
bb998419 | 426 | pr_info("verified up to eraseblock %u\n", i); |
2a6a28e7 RW |
427 | |
428 | err = mtdtest_relax(); | |
429 | if (err) | |
430 | goto out; | |
e73f2174 | 431 | } |
bb998419 | 432 | pr_info("verified %u eraseblocks\n", i); |
e73f2174 AB |
433 | |
434 | err = crosstest(); | |
435 | if (err) | |
436 | goto out; | |
437 | ||
438 | err = erasecrosstest(); | |
439 | if (err) | |
440 | goto out; | |
441 | ||
442 | err = erasetest(); | |
443 | if (err) | |
444 | goto out; | |
445 | ||
bb998419 | 446 | pr_info("finished with %d errors\n", errcnt); |
e73f2174 AB |
447 | out: |
448 | ||
449 | kfree(bbt); | |
450 | kfree(boundary); | |
451 | kfree(twopages); | |
452 | kfree(writebuf); | |
453 | put_mtd_device(mtd); | |
454 | if (err) | |
bb998419 | 455 | pr_info("error %d occurred\n", err); |
e73f2174 AB |
456 | printk(KERN_INFO "=================================================\n"); |
457 | return err; | |
458 | } | |
459 | module_init(mtd_pagetest_init); | |
460 | ||
461 | static void __exit mtd_pagetest_exit(void) | |
462 | { | |
463 | return; | |
464 | } | |
465 | module_exit(mtd_pagetest_exit); | |
466 | ||
467 | MODULE_DESCRIPTION("NAND page test"); | |
468 | MODULE_AUTHOR("Adrian Hunter"); | |
469 | MODULE_LICENSE("GPL"); |