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