| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 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 random reads, writes and erases on MTD device. | 
|  | 18 | * | 
|  | 19 | * Author: Adrian Hunter <ext-adrian.hunter@nokia.com> | 
|  | 20 | */ | 
|  | 21 |  | 
|  | 22 | #include <linux/init.h> | 
|  | 23 | #include <linux/module.h> | 
|  | 24 | #include <linux/moduleparam.h> | 
|  | 25 | #include <linux/err.h> | 
|  | 26 | #include <linux/mtd/mtd.h> | 
| Tejun Heo | 5a0e3ad | 2010-03-24 17:04:11 +0900 | [diff] [blame] | 27 | #include <linux/slab.h> | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 28 | #include <linux/sched.h> | 
|  | 29 | #include <linux/vmalloc.h> | 
|  | 30 |  | 
|  | 31 | #define PRINT_PREF KERN_INFO "mtd_stresstest: " | 
|  | 32 |  | 
| Wolfram Sang | 7406060 | 2011-10-30 00:11:53 +0200 | [diff] [blame] | 33 | static int dev = -EINVAL; | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 34 | module_param(dev, int, S_IRUGO); | 
|  | 35 | MODULE_PARM_DESC(dev, "MTD device number to use"); | 
|  | 36 |  | 
|  | 37 | static int count = 10000; | 
|  | 38 | module_param(count, int, S_IRUGO); | 
|  | 39 | MODULE_PARM_DESC(count, "Number of operations to do (default is 10000)"); | 
|  | 40 |  | 
|  | 41 | static struct mtd_info *mtd; | 
|  | 42 | static unsigned char *writebuf; | 
|  | 43 | static unsigned char *readbuf; | 
|  | 44 | static unsigned char *bbt; | 
|  | 45 | static int *offsets; | 
|  | 46 |  | 
|  | 47 | static int pgsize; | 
|  | 48 | static int bufsize; | 
|  | 49 | static int ebcnt; | 
|  | 50 | static int pgcnt; | 
|  | 51 | static unsigned long next = 1; | 
|  | 52 |  | 
|  | 53 | static inline unsigned int simple_rand(void) | 
|  | 54 | { | 
|  | 55 | next = next * 1103515245 + 12345; | 
|  | 56 | return (unsigned int)((next / 65536) % 32768); | 
|  | 57 | } | 
|  | 58 |  | 
|  | 59 | static inline void simple_srand(unsigned long seed) | 
|  | 60 | { | 
|  | 61 | next = seed; | 
|  | 62 | } | 
|  | 63 |  | 
|  | 64 | static int rand_eb(void) | 
|  | 65 | { | 
|  | 66 | int eb; | 
|  | 67 |  | 
|  | 68 | again: | 
|  | 69 | if (ebcnt < 32768) | 
|  | 70 | eb = simple_rand(); | 
|  | 71 | else | 
|  | 72 | eb = (simple_rand() << 15) | simple_rand(); | 
|  | 73 | /* Read or write up 2 eraseblocks at a time - hence 'ebcnt - 1' */ | 
|  | 74 | eb %= (ebcnt - 1); | 
|  | 75 | if (bbt[eb]) | 
|  | 76 | goto again; | 
|  | 77 | return eb; | 
|  | 78 | } | 
|  | 79 |  | 
|  | 80 | static int rand_offs(void) | 
|  | 81 | { | 
|  | 82 | int offs; | 
|  | 83 |  | 
|  | 84 | if (bufsize < 32768) | 
|  | 85 | offs = simple_rand(); | 
|  | 86 | else | 
|  | 87 | offs = (simple_rand() << 15) | simple_rand(); | 
|  | 88 | offs %= bufsize; | 
|  | 89 | return offs; | 
|  | 90 | } | 
|  | 91 |  | 
|  | 92 | static int rand_len(int offs) | 
|  | 93 | { | 
|  | 94 | int len; | 
|  | 95 |  | 
|  | 96 | if (bufsize < 32768) | 
|  | 97 | len = simple_rand(); | 
|  | 98 | else | 
|  | 99 | len = (simple_rand() << 15) | simple_rand(); | 
|  | 100 | len %= (bufsize - offs); | 
|  | 101 | return len; | 
|  | 102 | } | 
|  | 103 |  | 
|  | 104 | static int erase_eraseblock(int ebnum) | 
|  | 105 | { | 
|  | 106 | int err; | 
|  | 107 | struct erase_info ei; | 
|  | 108 | loff_t addr = ebnum * mtd->erasesize; | 
|  | 109 |  | 
|  | 110 | memset(&ei, 0, sizeof(struct erase_info)); | 
|  | 111 | ei.mtd  = mtd; | 
|  | 112 | ei.addr = addr; | 
|  | 113 | ei.len  = mtd->erasesize; | 
|  | 114 |  | 
|  | 115 | err = mtd->erase(mtd, &ei); | 
|  | 116 | if (unlikely(err)) { | 
|  | 117 | printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum); | 
|  | 118 | return err; | 
|  | 119 | } | 
|  | 120 |  | 
|  | 121 | if (unlikely(ei.state == MTD_ERASE_FAILED)) { | 
|  | 122 | printk(PRINT_PREF "some erase error occurred at EB %d\n", | 
|  | 123 | ebnum); | 
|  | 124 | return -EIO; | 
|  | 125 | } | 
|  | 126 |  | 
|  | 127 | return 0; | 
|  | 128 | } | 
|  | 129 |  | 
|  | 130 | static int is_block_bad(int ebnum) | 
|  | 131 | { | 
|  | 132 | loff_t addr = ebnum * mtd->erasesize; | 
|  | 133 | int ret; | 
|  | 134 |  | 
|  | 135 | ret = mtd->block_isbad(mtd, addr); | 
|  | 136 | if (ret) | 
|  | 137 | printk(PRINT_PREF "block %d is bad\n", ebnum); | 
|  | 138 | return ret; | 
|  | 139 | } | 
|  | 140 |  | 
|  | 141 | static int do_read(void) | 
|  | 142 | { | 
|  | 143 | size_t read = 0; | 
|  | 144 | int eb = rand_eb(); | 
|  | 145 | int offs = rand_offs(); | 
|  | 146 | int len = rand_len(offs), err; | 
|  | 147 | loff_t addr; | 
|  | 148 |  | 
|  | 149 | if (bbt[eb + 1]) { | 
|  | 150 | if (offs >= mtd->erasesize) | 
|  | 151 | offs -= mtd->erasesize; | 
|  | 152 | if (offs + len > mtd->erasesize) | 
|  | 153 | len = mtd->erasesize - offs; | 
|  | 154 | } | 
|  | 155 | addr = eb * mtd->erasesize + offs; | 
|  | 156 | err = mtd->read(mtd, addr, len, &read, readbuf); | 
| Brian Norris | d57f4054 | 2011-09-20 18:34:25 -0700 | [diff] [blame] | 157 | if (mtd_is_bitflip(err)) | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 158 | err = 0; | 
|  | 159 | if (unlikely(err || read != len)) { | 
|  | 160 | printk(PRINT_PREF "error: read failed at 0x%llx\n", | 
|  | 161 | (long long)addr); | 
|  | 162 | if (!err) | 
|  | 163 | err = -EINVAL; | 
|  | 164 | return err; | 
|  | 165 | } | 
|  | 166 | return 0; | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | static int do_write(void) | 
|  | 170 | { | 
|  | 171 | int eb = rand_eb(), offs, err, len; | 
|  | 172 | size_t written = 0; | 
|  | 173 | loff_t addr; | 
|  | 174 |  | 
|  | 175 | offs = offsets[eb]; | 
|  | 176 | if (offs >= mtd->erasesize) { | 
|  | 177 | err = erase_eraseblock(eb); | 
|  | 178 | if (err) | 
|  | 179 | return err; | 
|  | 180 | offs = offsets[eb] = 0; | 
|  | 181 | } | 
|  | 182 | len = rand_len(offs); | 
|  | 183 | len = ((len + pgsize - 1) / pgsize) * pgsize; | 
|  | 184 | if (offs + len > mtd->erasesize) { | 
|  | 185 | if (bbt[eb + 1]) | 
|  | 186 | len = mtd->erasesize - offs; | 
|  | 187 | else { | 
|  | 188 | err = erase_eraseblock(eb + 1); | 
|  | 189 | if (err) | 
|  | 190 | return err; | 
|  | 191 | offsets[eb + 1] = 0; | 
|  | 192 | } | 
|  | 193 | } | 
|  | 194 | addr = eb * mtd->erasesize + offs; | 
|  | 195 | err = mtd->write(mtd, addr, len, &written, writebuf); | 
|  | 196 | if (unlikely(err || written != len)) { | 
|  | 197 | printk(PRINT_PREF "error: write failed at 0x%llx\n", | 
|  | 198 | (long long)addr); | 
|  | 199 | if (!err) | 
|  | 200 | err = -EINVAL; | 
|  | 201 | return err; | 
|  | 202 | } | 
|  | 203 | offs += len; | 
|  | 204 | while (offs > mtd->erasesize) { | 
|  | 205 | offsets[eb++] = mtd->erasesize; | 
|  | 206 | offs -= mtd->erasesize; | 
|  | 207 | } | 
|  | 208 | offsets[eb] = offs; | 
|  | 209 | return 0; | 
|  | 210 | } | 
|  | 211 |  | 
|  | 212 | static int do_operation(void) | 
|  | 213 | { | 
|  | 214 | if (simple_rand() & 1) | 
|  | 215 | return do_read(); | 
|  | 216 | else | 
|  | 217 | return do_write(); | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 | static int scan_for_bad_eraseblocks(void) | 
|  | 221 | { | 
|  | 222 | int i, bad = 0; | 
|  | 223 |  | 
| Julia Lawall | 2bfefa4 | 2010-05-13 22:03:15 +0200 | [diff] [blame] | 224 | bbt = kzalloc(ebcnt, GFP_KERNEL); | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 225 | if (!bbt) { | 
|  | 226 | printk(PRINT_PREF "error: cannot allocate memory\n"); | 
|  | 227 | return -ENOMEM; | 
|  | 228 | } | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 229 |  | 
| Morten Thunberg Svendsen | f5e2bae | 2010-01-06 10:48:18 +0100 | [diff] [blame] | 230 | /* NOR flash does not implement block_isbad */ | 
|  | 231 | if (mtd->block_isbad == NULL) | 
|  | 232 | return 0; | 
|  | 233 |  | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 234 | printk(PRINT_PREF "scanning for bad eraseblocks\n"); | 
|  | 235 | for (i = 0; i < ebcnt; ++i) { | 
|  | 236 | bbt[i] = is_block_bad(i) ? 1 : 0; | 
|  | 237 | if (bbt[i]) | 
|  | 238 | bad += 1; | 
|  | 239 | cond_resched(); | 
|  | 240 | } | 
|  | 241 | printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad); | 
|  | 242 | return 0; | 
|  | 243 | } | 
|  | 244 |  | 
|  | 245 | static int __init mtd_stresstest_init(void) | 
|  | 246 | { | 
|  | 247 | int err; | 
|  | 248 | int i, op; | 
|  | 249 | uint64_t tmp; | 
|  | 250 |  | 
|  | 251 | printk(KERN_INFO "\n"); | 
|  | 252 | printk(KERN_INFO "=================================================\n"); | 
| Wolfram Sang | 7406060 | 2011-10-30 00:11:53 +0200 | [diff] [blame] | 253 |  | 
|  | 254 | if (dev < 0) { | 
|  | 255 | printk(PRINT_PREF "Please specify a valid mtd-device via module paramter\n"); | 
|  | 256 | printk(KERN_CRIT "CAREFUL: This test wipes all data on the specified MTD device!\n"); | 
|  | 257 | return -EINVAL; | 
|  | 258 | } | 
|  | 259 |  | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 260 | printk(PRINT_PREF "MTD device: %d\n", dev); | 
|  | 261 |  | 
|  | 262 | mtd = get_mtd_device(NULL, dev); | 
|  | 263 | if (IS_ERR(mtd)) { | 
|  | 264 | err = PTR_ERR(mtd); | 
|  | 265 | printk(PRINT_PREF "error: cannot get MTD device\n"); | 
|  | 266 | return err; | 
|  | 267 | } | 
|  | 268 |  | 
|  | 269 | if (mtd->writesize == 1) { | 
|  | 270 | printk(PRINT_PREF "not NAND flash, assume page size is 512 " | 
|  | 271 | "bytes.\n"); | 
|  | 272 | pgsize = 512; | 
|  | 273 | } else | 
|  | 274 | pgsize = mtd->writesize; | 
|  | 275 |  | 
|  | 276 | tmp = mtd->size; | 
|  | 277 | do_div(tmp, mtd->erasesize); | 
|  | 278 | ebcnt = tmp; | 
| Morten Thunberg Svendsen | f5e2bae | 2010-01-06 10:48:18 +0100 | [diff] [blame] | 279 | pgcnt = mtd->erasesize / pgsize; | 
| Artem Bityutskiy | 7163cea | 2008-12-08 13:36:47 +0200 | [diff] [blame] | 280 |  | 
|  | 281 | printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, " | 
|  | 282 | "page size %u, count of eraseblocks %u, pages per " | 
|  | 283 | "eraseblock %u, OOB size %u\n", | 
|  | 284 | (unsigned long long)mtd->size, mtd->erasesize, | 
|  | 285 | pgsize, ebcnt, pgcnt, mtd->oobsize); | 
|  | 286 |  | 
|  | 287 | /* Read or write up 2 eraseblocks at a time */ | 
|  | 288 | bufsize = mtd->erasesize * 2; | 
|  | 289 |  | 
|  | 290 | err = -ENOMEM; | 
|  | 291 | readbuf = vmalloc(bufsize); | 
|  | 292 | writebuf = vmalloc(bufsize); | 
|  | 293 | offsets = kmalloc(ebcnt * sizeof(int), GFP_KERNEL); | 
|  | 294 | if (!readbuf || !writebuf || !offsets) { | 
|  | 295 | printk(PRINT_PREF "error: cannot allocate memory\n"); | 
|  | 296 | goto out; | 
|  | 297 | } | 
|  | 298 | for (i = 0; i < ebcnt; i++) | 
|  | 299 | offsets[i] = mtd->erasesize; | 
|  | 300 | simple_srand(current->pid); | 
|  | 301 | for (i = 0; i < bufsize; i++) | 
|  | 302 | writebuf[i] = simple_rand(); | 
|  | 303 |  | 
|  | 304 | err = scan_for_bad_eraseblocks(); | 
|  | 305 | if (err) | 
|  | 306 | goto out; | 
|  | 307 |  | 
|  | 308 | /* Do operations */ | 
|  | 309 | printk(PRINT_PREF "doing operations\n"); | 
|  | 310 | for (op = 0; op < count; op++) { | 
|  | 311 | if ((op & 1023) == 0) | 
|  | 312 | printk(PRINT_PREF "%d operations done\n", op); | 
|  | 313 | err = do_operation(); | 
|  | 314 | if (err) | 
|  | 315 | goto out; | 
|  | 316 | cond_resched(); | 
|  | 317 | } | 
|  | 318 | printk(PRINT_PREF "finished, %d operations done\n", op); | 
|  | 319 |  | 
|  | 320 | out: | 
|  | 321 | kfree(offsets); | 
|  | 322 | kfree(bbt); | 
|  | 323 | vfree(writebuf); | 
|  | 324 | vfree(readbuf); | 
|  | 325 | put_mtd_device(mtd); | 
|  | 326 | if (err) | 
|  | 327 | printk(PRINT_PREF "error %d occurred\n", err); | 
|  | 328 | printk(KERN_INFO "=================================================\n"); | 
|  | 329 | return err; | 
|  | 330 | } | 
|  | 331 | module_init(mtd_stresstest_init); | 
|  | 332 |  | 
|  | 333 | static void __exit mtd_stresstest_exit(void) | 
|  | 334 | { | 
|  | 335 | return; | 
|  | 336 | } | 
|  | 337 | module_exit(mtd_stresstest_exit); | 
|  | 338 |  | 
|  | 339 | MODULE_DESCRIPTION("Stress test module"); | 
|  | 340 | MODULE_AUTHOR("Adrian Hunter"); | 
|  | 341 | MODULE_LICENSE("GPL"); |