]>
Commit | Line | Data |
---|---|---|
72091b68 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 | * Check MTD device read. | |
18 | * | |
19 | * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> | |
20 | */ | |
21 | ||
e45048a6 VN |
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
23 | ||
72091b68 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> |
72091b68 AB |
30 | #include <linux/sched.h> |
31 | ||
7406060e | 32 | static int dev = -EINVAL; |
72091b68 AB |
33 | module_param(dev, int, S_IRUGO); |
34 | MODULE_PARM_DESC(dev, "MTD device number to use"); | |
35 | ||
36 | static struct mtd_info *mtd; | |
37 | static unsigned char *iobuf; | |
38 | static unsigned char *iobuf1; | |
39 | static unsigned char *bbt; | |
40 | ||
41 | static int pgsize; | |
42 | static int ebcnt; | |
43 | static int pgcnt; | |
44 | ||
45 | static int read_eraseblock_by_page(int ebnum) | |
46 | { | |
30fa9848 | 47 | size_t read; |
72091b68 AB |
48 | int i, ret, err = 0; |
49 | loff_t addr = ebnum * mtd->erasesize; | |
50 | void *buf = iobuf; | |
51 | void *oobbuf = iobuf1; | |
52 | ||
53 | for (i = 0; i < pgcnt; i++) { | |
d8b1e34e | 54 | memset(buf, 0 , pgsize); |
329ad399 | 55 | ret = mtd_read(mtd, addr, pgsize, &read, buf); |
72091b68 AB |
56 | if (ret == -EUCLEAN) |
57 | ret = 0; | |
58 | if (ret || read != pgsize) { | |
e45048a6 | 59 | pr_err("error: read failed at %#llx\n", |
72091b68 AB |
60 | (long long)addr); |
61 | if (!err) | |
62 | err = ret; | |
63 | if (!err) | |
64 | err = -EINVAL; | |
65 | } | |
66 | if (mtd->oobsize) { | |
67 | struct mtd_oob_ops ops; | |
68 | ||
0612b9dd | 69 | ops.mode = MTD_OPS_PLACE_OOB; |
72091b68 AB |
70 | ops.len = 0; |
71 | ops.retlen = 0; | |
72 | ops.ooblen = mtd->oobsize; | |
73 | ops.oobretlen = 0; | |
74 | ops.ooboffs = 0; | |
23d42494 | 75 | ops.datbuf = NULL; |
72091b68 | 76 | ops.oobbuf = oobbuf; |
fd2819bb | 77 | ret = mtd_read_oob(mtd, addr, &ops); |
d57f4054 | 78 | if ((ret && !mtd_is_bitflip(ret)) || |
003bc479 | 79 | ops.oobretlen != mtd->oobsize) { |
e45048a6 | 80 | pr_err("error: read oob failed at " |
72091b68 AB |
81 | "%#llx\n", (long long)addr); |
82 | if (!err) | |
83 | err = ret; | |
84 | if (!err) | |
85 | err = -EINVAL; | |
86 | } | |
87 | oobbuf += mtd->oobsize; | |
88 | } | |
89 | addr += pgsize; | |
90 | buf += pgsize; | |
91 | } | |
92 | ||
93 | return err; | |
94 | } | |
95 | ||
96 | static void dump_eraseblock(int ebnum) | |
97 | { | |
98 | int i, j, n; | |
99 | char line[128]; | |
100 | int pg, oob; | |
101 | ||
e45048a6 | 102 | pr_info("dumping eraseblock %d\n", ebnum); |
72091b68 AB |
103 | n = mtd->erasesize; |
104 | for (i = 0; i < n;) { | |
105 | char *p = line; | |
106 | ||
107 | p += sprintf(p, "%05x: ", i); | |
108 | for (j = 0; j < 32 && i < n; j++, i++) | |
109 | p += sprintf(p, "%02x", (unsigned int)iobuf[i]); | |
110 | printk(KERN_CRIT "%s\n", line); | |
111 | cond_resched(); | |
112 | } | |
113 | if (!mtd->oobsize) | |
114 | return; | |
e45048a6 | 115 | pr_info("dumping oob from eraseblock %d\n", ebnum); |
72091b68 AB |
116 | n = mtd->oobsize; |
117 | for (pg = 0, i = 0; pg < pgcnt; pg++) | |
118 | for (oob = 0; oob < n;) { | |
119 | char *p = line; | |
120 | ||
121 | p += sprintf(p, "%05x: ", i); | |
122 | for (j = 0; j < 32 && oob < n; j++, oob++, i++) | |
123 | p += sprintf(p, "%02x", | |
124 | (unsigned int)iobuf1[i]); | |
125 | printk(KERN_CRIT "%s\n", line); | |
126 | cond_resched(); | |
127 | } | |
128 | } | |
129 | ||
130 | static int is_block_bad(int ebnum) | |
131 | { | |
132 | loff_t addr = ebnum * mtd->erasesize; | |
133 | int ret; | |
134 | ||
7086c19d | 135 | ret = mtd_block_isbad(mtd, addr); |
72091b68 | 136 | if (ret) |
e45048a6 | 137 | pr_info("block %d is bad\n", ebnum); |
72091b68 AB |
138 | return ret; |
139 | } | |
140 | ||
141 | static int scan_for_bad_eraseblocks(void) | |
142 | { | |
143 | int i, bad = 0; | |
144 | ||
2bfefa4c | 145 | bbt = kzalloc(ebcnt, GFP_KERNEL); |
33777e66 | 146 | if (!bbt) |
72091b68 | 147 | return -ENOMEM; |
72091b68 | 148 | |
8f461a73 | 149 | if (!mtd_can_have_bb(mtd)) |
f5e2bae0 MTS |
150 | return 0; |
151 | ||
e45048a6 | 152 | pr_info("scanning for bad eraseblocks\n"); |
72091b68 AB |
153 | for (i = 0; i < ebcnt; ++i) { |
154 | bbt[i] = is_block_bad(i) ? 1 : 0; | |
155 | if (bbt[i]) | |
156 | bad += 1; | |
157 | cond_resched(); | |
158 | } | |
e45048a6 | 159 | pr_info("scanned %d eraseblocks, %d are bad\n", i, bad); |
72091b68 AB |
160 | return 0; |
161 | } | |
162 | ||
163 | static int __init mtd_readtest_init(void) | |
164 | { | |
165 | uint64_t tmp; | |
166 | int err, i; | |
167 | ||
168 | printk(KERN_INFO "\n"); | |
169 | printk(KERN_INFO "=================================================\n"); | |
7406060e WS |
170 | |
171 | if (dev < 0) { | |
064a7694 | 172 | pr_info("Please specify a valid mtd-device via module parameter\n"); |
7406060e WS |
173 | return -EINVAL; |
174 | } | |
175 | ||
e45048a6 | 176 | pr_info("MTD device: %d\n", dev); |
72091b68 AB |
177 | |
178 | mtd = get_mtd_device(NULL, dev); | |
179 | if (IS_ERR(mtd)) { | |
180 | err = PTR_ERR(mtd); | |
e45048a6 | 181 | pr_err("error: Cannot get MTD device\n"); |
72091b68 AB |
182 | return err; |
183 | } | |
184 | ||
185 | if (mtd->writesize == 1) { | |
e45048a6 | 186 | pr_info("not NAND flash, assume page size is 512 " |
72091b68 AB |
187 | "bytes.\n"); |
188 | pgsize = 512; | |
189 | } else | |
190 | pgsize = mtd->writesize; | |
191 | ||
192 | tmp = mtd->size; | |
193 | do_div(tmp, mtd->erasesize); | |
194 | ebcnt = tmp; | |
f5e2bae0 | 195 | pgcnt = mtd->erasesize / pgsize; |
72091b68 | 196 | |
e45048a6 | 197 | pr_info("MTD device size %llu, eraseblock size %u, " |
72091b68 AB |
198 | "page size %u, count of eraseblocks %u, pages per " |
199 | "eraseblock %u, OOB size %u\n", | |
200 | (unsigned long long)mtd->size, mtd->erasesize, | |
201 | pgsize, ebcnt, pgcnt, mtd->oobsize); | |
202 | ||
203 | err = -ENOMEM; | |
204 | iobuf = kmalloc(mtd->erasesize, GFP_KERNEL); | |
33777e66 | 205 | if (!iobuf) |
72091b68 | 206 | goto out; |
72091b68 | 207 | iobuf1 = kmalloc(mtd->erasesize, GFP_KERNEL); |
33777e66 | 208 | if (!iobuf1) |
72091b68 | 209 | goto out; |
72091b68 AB |
210 | |
211 | err = scan_for_bad_eraseblocks(); | |
212 | if (err) | |
213 | goto out; | |
214 | ||
215 | /* Read all eraseblocks 1 page at a time */ | |
e45048a6 | 216 | pr_info("testing page read\n"); |
72091b68 AB |
217 | for (i = 0; i < ebcnt; ++i) { |
218 | int ret; | |
219 | ||
220 | if (bbt[i]) | |
221 | continue; | |
222 | ret = read_eraseblock_by_page(i); | |
223 | if (ret) { | |
224 | dump_eraseblock(i); | |
225 | if (!err) | |
226 | err = ret; | |
227 | } | |
228 | cond_resched(); | |
229 | } | |
230 | ||
231 | if (err) | |
e45048a6 | 232 | pr_info("finished with errors\n"); |
72091b68 | 233 | else |
e45048a6 | 234 | pr_info("finished\n"); |
72091b68 AB |
235 | |
236 | out: | |
237 | ||
238 | kfree(iobuf); | |
239 | kfree(iobuf1); | |
240 | kfree(bbt); | |
241 | put_mtd_device(mtd); | |
242 | if (err) | |
e45048a6 | 243 | pr_info("error %d occurred\n", err); |
72091b68 AB |
244 | printk(KERN_INFO "=================================================\n"); |
245 | return err; | |
246 | } | |
247 | module_init(mtd_readtest_init); | |
248 | ||
249 | static void __exit mtd_readtest_exit(void) | |
250 | { | |
251 | return; | |
252 | } | |
253 | module_exit(mtd_readtest_exit); | |
254 | ||
255 | MODULE_DESCRIPTION("Read test module"); | |
256 | MODULE_AUTHOR("Adrian Hunter"); | |
257 | MODULE_LICENSE("GPL"); |