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