1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2007 Nokia Corporation |
4 | * |
5 | * Test read and write speed of a MTD device. |
6 | * |
7 | * Author: Adrian Hunter <adrian.hunter@nokia.com> |
8 | */ |
9 | |
10 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
11 | |
12 | #include <linux/init.h> |
13 | #include <linux/ktime.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 int count; |
29 | module_param(count, int, S_IRUGO); |
30 | MODULE_PARM_DESC(count, "Maximum number of eraseblocks to use " |
31 | "(0 means use all)" ); |
32 | |
33 | static struct mtd_info *mtd; |
34 | static unsigned char *iobuf; |
35 | static unsigned char *bbt; |
36 | |
37 | static int pgsize; |
38 | static int ebcnt; |
39 | static int pgcnt; |
40 | static int goodebcnt; |
41 | static ktime_t start, finish; |
42 | |
43 | static int multiblock_erase(int ebnum, int blocks) |
44 | { |
45 | int err; |
46 | struct erase_info ei; |
47 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
48 | |
49 | memset(&ei, 0, sizeof(struct erase_info)); |
50 | ei.addr = addr; |
51 | ei.len = mtd->erasesize * blocks; |
52 | |
53 | err = mtd_erase(mtd, instr: &ei); |
54 | if (err) { |
55 | pr_err("error %d while erasing EB %d, blocks %d\n" , |
56 | err, ebnum, blocks); |
57 | return err; |
58 | } |
59 | |
60 | return 0; |
61 | } |
62 | |
63 | static int write_eraseblock(int ebnum) |
64 | { |
65 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
66 | |
67 | return mtdtest_write(mtd, addr, size: mtd->erasesize, buf: iobuf); |
68 | } |
69 | |
70 | static int write_eraseblock_by_page(int ebnum) |
71 | { |
72 | int i, err = 0; |
73 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
74 | void *buf = iobuf; |
75 | |
76 | for (i = 0; i < pgcnt; i++) { |
77 | err = mtdtest_write(mtd, addr, size: pgsize, buf); |
78 | if (err) |
79 | break; |
80 | addr += pgsize; |
81 | buf += pgsize; |
82 | } |
83 | |
84 | return err; |
85 | } |
86 | |
87 | static int write_eraseblock_by_2pages(int ebnum) |
88 | { |
89 | size_t sz = pgsize * 2; |
90 | int i, n = pgcnt / 2, err = 0; |
91 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
92 | void *buf = iobuf; |
93 | |
94 | for (i = 0; i < n; i++) { |
95 | err = mtdtest_write(mtd, addr, size: sz, buf); |
96 | if (err) |
97 | return err; |
98 | addr += sz; |
99 | buf += sz; |
100 | } |
101 | if (pgcnt % 2) |
102 | err = mtdtest_write(mtd, addr, size: pgsize, buf); |
103 | |
104 | return err; |
105 | } |
106 | |
107 | static int read_eraseblock(int ebnum) |
108 | { |
109 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
110 | |
111 | return mtdtest_read(mtd, addr, size: mtd->erasesize, buf: iobuf); |
112 | } |
113 | |
114 | static int read_eraseblock_by_page(int ebnum) |
115 | { |
116 | int i, err = 0; |
117 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
118 | void *buf = iobuf; |
119 | |
120 | for (i = 0; i < pgcnt; i++) { |
121 | err = mtdtest_read(mtd, addr, size: pgsize, buf); |
122 | if (err) |
123 | break; |
124 | addr += pgsize; |
125 | buf += pgsize; |
126 | } |
127 | |
128 | return err; |
129 | } |
130 | |
131 | static int read_eraseblock_by_2pages(int ebnum) |
132 | { |
133 | size_t sz = pgsize * 2; |
134 | int i, n = pgcnt / 2, err = 0; |
135 | loff_t addr = (loff_t)ebnum * mtd->erasesize; |
136 | void *buf = iobuf; |
137 | |
138 | for (i = 0; i < n; i++) { |
139 | err = mtdtest_read(mtd, addr, size: sz, buf); |
140 | if (err) |
141 | return err; |
142 | addr += sz; |
143 | buf += sz; |
144 | } |
145 | if (pgcnt % 2) |
146 | err = mtdtest_read(mtd, addr, size: pgsize, buf); |
147 | |
148 | return err; |
149 | } |
150 | |
151 | static inline void start_timing(void) |
152 | { |
153 | start = ktime_get(); |
154 | } |
155 | |
156 | static inline void stop_timing(void) |
157 | { |
158 | finish = ktime_get(); |
159 | } |
160 | |
161 | static long calc_speed(void) |
162 | { |
163 | uint64_t k, us; |
164 | |
165 | us = ktime_us_delta(later: finish, earlier: start); |
166 | if (us == 0) |
167 | return 0; |
168 | k = (uint64_t)goodebcnt * (mtd->erasesize / 1024) * 1000000; |
169 | do_div(k, us); |
170 | return k; |
171 | } |
172 | |
173 | static int __init mtd_speedtest_init(void) |
174 | { |
175 | int err, i, blocks, j, k; |
176 | long speed; |
177 | uint64_t tmp; |
178 | |
179 | printk(KERN_INFO "\n" ); |
180 | printk(KERN_INFO "=================================================\n" ); |
181 | |
182 | if (dev < 0) { |
183 | pr_info("Please specify a valid mtd-device via module parameter\n" ); |
184 | pr_crit("CAREFUL: This test wipes all data on the specified MTD device!\n" ); |
185 | return -EINVAL; |
186 | } |
187 | |
188 | if (count) |
189 | pr_info("MTD device: %d count: %d\n" , dev, count); |
190 | else |
191 | pr_info("MTD device: %d\n" , dev); |
192 | |
193 | mtd = get_mtd_device(NULL, num: dev); |
194 | if (IS_ERR(ptr: mtd)) { |
195 | err = PTR_ERR(ptr: mtd); |
196 | pr_err("error: cannot get MTD device\n" ); |
197 | return err; |
198 | } |
199 | |
200 | if (mtd->writesize == 1) { |
201 | pr_info("not NAND flash, assume page size is 512 " |
202 | "bytes.\n" ); |
203 | pgsize = 512; |
204 | } else |
205 | pgsize = mtd->writesize; |
206 | |
207 | tmp = mtd->size; |
208 | do_div(tmp, mtd->erasesize); |
209 | ebcnt = tmp; |
210 | pgcnt = mtd->erasesize / pgsize; |
211 | |
212 | pr_info("MTD device size %llu, eraseblock size %u, " |
213 | "page size %u, count of eraseblocks %u, pages per " |
214 | "eraseblock %u, OOB size %u\n" , |
215 | (unsigned long long)mtd->size, mtd->erasesize, |
216 | pgsize, ebcnt, pgcnt, mtd->oobsize); |
217 | |
218 | if (count > 0 && count < ebcnt) |
219 | ebcnt = count; |
220 | |
221 | err = -ENOMEM; |
222 | iobuf = kmalloc(size: mtd->erasesize, GFP_KERNEL); |
223 | if (!iobuf) |
224 | goto out; |
225 | |
226 | get_random_bytes(buf: iobuf, len: mtd->erasesize); |
227 | |
228 | bbt = kzalloc(size: ebcnt, GFP_KERNEL); |
229 | if (!bbt) |
230 | goto out; |
231 | err = mtdtest_scan_for_bad_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
232 | if (err) |
233 | goto out; |
234 | for (i = 0; i < ebcnt; i++) { |
235 | if (!bbt[i]) |
236 | goodebcnt++; |
237 | } |
238 | |
239 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
240 | if (err) |
241 | goto out; |
242 | |
243 | /* Write all eraseblocks, 1 eraseblock at a time */ |
244 | pr_info("testing eraseblock write speed\n" ); |
245 | start_timing(); |
246 | for (i = 0; i < ebcnt; ++i) { |
247 | if (bbt[i]) |
248 | continue; |
249 | err = write_eraseblock(ebnum: i); |
250 | if (err) |
251 | goto out; |
252 | |
253 | err = mtdtest_relax(); |
254 | if (err) |
255 | goto out; |
256 | } |
257 | stop_timing(); |
258 | speed = calc_speed(); |
259 | pr_info("eraseblock write speed is %ld KiB/s\n" , speed); |
260 | |
261 | /* Read all eraseblocks, 1 eraseblock at a time */ |
262 | pr_info("testing eraseblock read speed\n" ); |
263 | start_timing(); |
264 | for (i = 0; i < ebcnt; ++i) { |
265 | if (bbt[i]) |
266 | continue; |
267 | err = read_eraseblock(ebnum: i); |
268 | if (err) |
269 | goto out; |
270 | |
271 | err = mtdtest_relax(); |
272 | if (err) |
273 | goto out; |
274 | } |
275 | stop_timing(); |
276 | speed = calc_speed(); |
277 | pr_info("eraseblock read speed is %ld KiB/s\n" , speed); |
278 | |
279 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
280 | if (err) |
281 | goto out; |
282 | |
283 | /* Write all eraseblocks, 1 page at a time */ |
284 | pr_info("testing page write speed\n" ); |
285 | start_timing(); |
286 | for (i = 0; i < ebcnt; ++i) { |
287 | if (bbt[i]) |
288 | continue; |
289 | err = write_eraseblock_by_page(ebnum: i); |
290 | if (err) |
291 | goto out; |
292 | |
293 | err = mtdtest_relax(); |
294 | if (err) |
295 | goto out; |
296 | } |
297 | stop_timing(); |
298 | speed = calc_speed(); |
299 | pr_info("page write speed is %ld KiB/s\n" , speed); |
300 | |
301 | /* Read all eraseblocks, 1 page at a time */ |
302 | pr_info("testing page read speed\n" ); |
303 | start_timing(); |
304 | for (i = 0; i < ebcnt; ++i) { |
305 | if (bbt[i]) |
306 | continue; |
307 | err = read_eraseblock_by_page(ebnum: i); |
308 | if (err) |
309 | goto out; |
310 | |
311 | err = mtdtest_relax(); |
312 | if (err) |
313 | goto out; |
314 | } |
315 | stop_timing(); |
316 | speed = calc_speed(); |
317 | pr_info("page read speed is %ld KiB/s\n" , speed); |
318 | |
319 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
320 | if (err) |
321 | goto out; |
322 | |
323 | /* Write all eraseblocks, 2 pages at a time */ |
324 | pr_info("testing 2 page write speed\n" ); |
325 | start_timing(); |
326 | for (i = 0; i < ebcnt; ++i) { |
327 | if (bbt[i]) |
328 | continue; |
329 | err = write_eraseblock_by_2pages(ebnum: i); |
330 | if (err) |
331 | goto out; |
332 | |
333 | err = mtdtest_relax(); |
334 | if (err) |
335 | goto out; |
336 | } |
337 | stop_timing(); |
338 | speed = calc_speed(); |
339 | pr_info("2 page write speed is %ld KiB/s\n" , speed); |
340 | |
341 | /* Read all eraseblocks, 2 pages at a time */ |
342 | pr_info("testing 2 page read speed\n" ); |
343 | start_timing(); |
344 | for (i = 0; i < ebcnt; ++i) { |
345 | if (bbt[i]) |
346 | continue; |
347 | err = read_eraseblock_by_2pages(ebnum: i); |
348 | if (err) |
349 | goto out; |
350 | |
351 | err = mtdtest_relax(); |
352 | if (err) |
353 | goto out; |
354 | } |
355 | stop_timing(); |
356 | speed = calc_speed(); |
357 | pr_info("2 page read speed is %ld KiB/s\n" , speed); |
358 | |
359 | /* Erase all eraseblocks */ |
360 | pr_info("Testing erase speed\n" ); |
361 | start_timing(); |
362 | err = mtdtest_erase_good_eraseblocks(mtd, bbt, eb: 0, ebcnt); |
363 | if (err) |
364 | goto out; |
365 | stop_timing(); |
366 | speed = calc_speed(); |
367 | pr_info("erase speed is %ld KiB/s\n" , speed); |
368 | |
369 | /* Multi-block erase all eraseblocks */ |
370 | for (k = 1; k < 7; k++) { |
371 | blocks = 1 << k; |
372 | pr_info("Testing %dx multi-block erase speed\n" , |
373 | blocks); |
374 | start_timing(); |
375 | for (i = 0; i < ebcnt; ) { |
376 | for (j = 0; j < blocks && (i + j) < ebcnt; j++) |
377 | if (bbt[i + j]) |
378 | break; |
379 | if (j < 1) { |
380 | i++; |
381 | continue; |
382 | } |
383 | err = multiblock_erase(ebnum: i, blocks: j); |
384 | if (err) |
385 | goto out; |
386 | |
387 | err = mtdtest_relax(); |
388 | if (err) |
389 | goto out; |
390 | |
391 | i += j; |
392 | } |
393 | stop_timing(); |
394 | speed = calc_speed(); |
395 | pr_info("%dx multi-block erase speed is %ld KiB/s\n" , |
396 | blocks, speed); |
397 | } |
398 | pr_info("finished\n" ); |
399 | out: |
400 | kfree(objp: iobuf); |
401 | kfree(objp: bbt); |
402 | put_mtd_device(mtd); |
403 | if (err) |
404 | pr_info("error %d occurred\n" , err); |
405 | printk(KERN_INFO "=================================================\n" ); |
406 | return err; |
407 | } |
408 | module_init(mtd_speedtest_init); |
409 | |
410 | static void __exit mtd_speedtest_exit(void) |
411 | { |
412 | return; |
413 | } |
414 | module_exit(mtd_speedtest_exit); |
415 | |
416 | MODULE_DESCRIPTION("Speed test module" ); |
417 | MODULE_AUTHOR("Adrian Hunter" ); |
418 | MODULE_LICENSE("GPL" ); |
419 | |