1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2008 Nokia Corporation |
4 | * |
5 | * Test random reads, writes and erases 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 <linux/init.h> |
13 | #include <linux/module.h> |
14 | #include <linux/moduleparam.h> |
15 | #include <linux/err.h> |
16 | #include <linux/mtd/mtd.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/sched.h> |
19 | #include <linux/vmalloc.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 int count = 10000; |
29 | module_param(count, int, S_IRUGO); |
30 | MODULE_PARM_DESC(count, "Number of operations to do (default is 10000)" ); |
31 | |
32 | static struct mtd_info *mtd; |
33 | static unsigned char *writebuf; |
34 | static unsigned char *readbuf; |
35 | static unsigned char *bbt; |
36 | static int *offsets; |
37 | |
38 | static int pgsize; |
39 | static int bufsize; |
40 | static int ebcnt; |
41 | static int pgcnt; |
42 | |
43 | static int rand_eb(void) |
44 | { |
45 | unsigned int eb; |
46 | |
47 | again: |
48 | /* Read or write up 2 eraseblocks at a time - hence 'ebcnt - 1' */ |
49 | eb = get_random_u32_below(ceil: ebcnt - 1); |
50 | if (bbt[eb]) |
51 | goto again; |
52 | return eb; |
53 | } |
54 | |
55 | static int rand_offs(void) |
56 | { |
57 | return get_random_u32_below(ceil: bufsize); |
58 | } |
59 | |
60 | static int rand_len(int offs) |
61 | { |
62 | return get_random_u32_below(ceil: bufsize - offs); |
63 | } |
64 | |
65 | static int do_read(void) |
66 | { |
67 | int eb = rand_eb(); |
68 | int offs = rand_offs(); |
69 | int len = rand_len(offs); |
70 | loff_t addr; |
71 | |
72 | if (bbt[eb + 1]) { |
73 | if (offs >= mtd->erasesize) |
74 | offs -= mtd->erasesize; |
75 | if (offs + len > mtd->erasesize) |
76 | len = mtd->erasesize - offs; |
77 | } |
78 | addr = (loff_t)eb * mtd->erasesize + offs; |
79 | return mtdtest_read(mtd, addr, size: len, buf: readbuf); |
80 | } |
81 | |
82 | static int do_write(void) |
83 | { |
84 | int eb = rand_eb(), offs, err, len; |
85 | loff_t addr; |
86 | |
87 | offs = offsets[eb]; |
88 | if (offs >= mtd->erasesize) { |
89 | err = mtdtest_erase_eraseblock(mtd, ebnum: eb); |
90 | if (err) |
91 | return err; |
92 | offs = offsets[eb] = 0; |
93 | } |
94 | len = rand_len(offs); |
95 | len = ((len + pgsize - 1) / pgsize) * pgsize; |
96 | if (offs + len > mtd->erasesize) { |
97 | if (bbt[eb + 1]) |
98 | len = mtd->erasesize - offs; |
99 | else { |
100 | err = mtdtest_erase_eraseblock(mtd, ebnum: eb + 1); |
101 | if (err) |
102 | return err; |
103 | offsets[eb + 1] = 0; |
104 | } |
105 | } |
106 | addr = (loff_t)eb * mtd->erasesize + offs; |
107 | err = mtdtest_write(mtd, addr, size: len, buf: writebuf); |
108 | if (unlikely(err)) |
109 | return err; |
110 | offs += len; |
111 | while (offs > mtd->erasesize) { |
112 | offsets[eb++] = mtd->erasesize; |
113 | offs -= mtd->erasesize; |
114 | } |
115 | offsets[eb] = offs; |
116 | return 0; |
117 | } |
118 | |
119 | static int do_operation(void) |
120 | { |
121 | if (get_random_u32_below(ceil: 2)) |
122 | return do_read(); |
123 | else |
124 | return do_write(); |
125 | } |
126 | |
127 | static int __init mtd_stresstest_init(void) |
128 | { |
129 | int err; |
130 | int i, op; |
131 | uint64_t tmp; |
132 | |
133 | printk(KERN_INFO "\n" ); |
134 | printk(KERN_INFO "=================================================\n" ); |
135 | |
136 | if (dev < 0) { |
137 | pr_info("Please specify a valid mtd-device via module parameter\n" ); |
138 | pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n" ); |
139 | return -EINVAL; |
140 | } |
141 | |
142 | pr_info("MTD device: %d\n" , dev); |
143 | |
144 | mtd = get_mtd_device(NULL, num: dev); |
145 | if (IS_ERR(ptr: mtd)) { |
146 | err = PTR_ERR(ptr: mtd); |
147 | pr_err("error: cannot get MTD device\n" ); |
148 | return err; |
149 | } |
150 | |
151 | if (mtd->writesize == 1) { |
152 | pr_info("not NAND flash, assume page size is 512 " |
153 | "bytes.\n" ); |
154 | pgsize = 512; |
155 | } else |
156 | pgsize = mtd->writesize; |
157 | |
158 | tmp = mtd->size; |
159 | do_div(tmp, mtd->erasesize); |
160 | ebcnt = tmp; |
161 | pgcnt = mtd->erasesize / pgsize; |
162 | |
163 | pr_info("MTD device size %llu, eraseblock size %u, " |
164 | "page size %u, count of eraseblocks %u, pages per " |
165 | "eraseblock %u, OOB size %u\n" , |
166 | (unsigned long long)mtd->size, mtd->erasesize, |
167 | pgsize, ebcnt, pgcnt, mtd->oobsize); |
168 | |
169 | if (ebcnt < 2) { |
170 | pr_err("error: need at least 2 eraseblocks\n" ); |
171 | err = -ENOSPC; |
172 | goto out_put_mtd; |
173 | } |
174 | |
175 | /* Read or write up 2 eraseblocks at a time */ |
176 | bufsize = mtd->erasesize * 2; |
177 | |
178 | err = -ENOMEM; |
179 | readbuf = vmalloc(size: bufsize); |
180 | writebuf = vmalloc(size: bufsize); |
181 | offsets = kmalloc_array(n: ebcnt, size: sizeof(int), GFP_KERNEL); |
182 | if (!readbuf || !writebuf || !offsets) |
183 | goto out; |
184 | for (i = 0; i < ebcnt; i++) |
185 | offsets[i] = mtd->erasesize; |
186 | get_random_bytes(buf: writebuf, len: bufsize); |
187 | |
188 | bbt = kzalloc(size: ebcnt, GFP_KERNEL); |
189 | if (!bbt) |
190 | goto out; |
191 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
192 | if (err) |
193 | goto out; |
194 | |
195 | /* Do operations */ |
196 | pr_info("doing operations\n" ); |
197 | for (op = 0; op < count; op++) { |
198 | if ((op & 1023) == 0) |
199 | pr_info("%d operations done\n" , op); |
200 | err = do_operation(); |
201 | if (err) |
202 | goto out; |
203 | |
204 | err = mtdtest_relax(); |
205 | if (err) |
206 | goto out; |
207 | } |
208 | pr_info("finished, %d operations done\n" , op); |
209 | |
210 | out: |
211 | kfree(objp: offsets); |
212 | kfree(objp: bbt); |
213 | vfree(addr: writebuf); |
214 | vfree(addr: readbuf); |
215 | out_put_mtd: |
216 | put_mtd_device(mtd); |
217 | if (err) |
218 | pr_info("error %d occurred\n" , err); |
219 | printk(KERN_INFO "=================================================\n" ); |
220 | return err; |
221 | } |
222 | module_init(mtd_stresstest_init); |
223 | |
224 | static void __exit mtd_stresstest_exit(void) |
225 | { |
226 | return; |
227 | } |
228 | module_exit(mtd_stresstest_exit); |
229 | |
230 | MODULE_DESCRIPTION("Stress test module" ); |
231 | MODULE_AUTHOR("Adrian Hunter" ); |
232 | MODULE_LICENSE("GPL" ); |
233 | |