diff options
author | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-02-04 18:51:41 -0500 |
---|---|---|
committer | Paul E. McKenney <paulmck@linux.vnet.ibm.com> | 2014-02-23 12:04:29 -0500 |
commit | 0af3fe1efa534a43385fe2694c42ffec7a310e46 (patch) | |
tree | 32a167b7b5cae8a461569ca5b58433b1400ac5b8 /kernel/locking | |
parent | 2193e1604eac422df05f77b53667237fcf130bf5 (diff) |
locktorture: Add a lock-torture kernel module
This commit adds the locking counterpart to rcutorture.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
[ paulmck: Make n_lock_torture_errors and torture_spinlock static
as suggested by Fengguang Wu. ]
Reviewed-by: Josh Triplett <josh@joshtriplett.org>
Diffstat (limited to 'kernel/locking')
-rw-r--r-- | kernel/locking/Makefile | 1 | ||||
-rw-r--r-- | kernel/locking/locktorture.c | 421 |
2 files changed, 422 insertions, 0 deletions
diff --git a/kernel/locking/Makefile b/kernel/locking/Makefile index baab8e5e7f66..a28ea6d9e6e8 100644 --- a/kernel/locking/Makefile +++ b/kernel/locking/Makefile | |||
@@ -23,3 +23,4 @@ obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock_debug.o | |||
23 | obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o | 23 | obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o |
24 | obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.o | 24 | obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.o |
25 | obj-$(CONFIG_PERCPU_RWSEM) += percpu-rwsem.o | 25 | obj-$(CONFIG_PERCPU_RWSEM) += percpu-rwsem.o |
26 | obj-$(CONFIG_LOCK_TORTURE_TEST) += locktorture.o | ||
diff --git a/kernel/locking/locktorture.c b/kernel/locking/locktorture.c new file mode 100644 index 000000000000..d69d20d9c9db --- /dev/null +++ b/kernel/locking/locktorture.c | |||
@@ -0,0 +1,421 @@ | |||
1 | /* | ||
2 | * Module-based torture test facility for locking | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, you can access it online at | ||
16 | * http://www.gnu.org/licenses/gpl-2.0.html. | ||
17 | * | ||
18 | * Copyright (C) IBM Corporation, 2014 | ||
19 | * | ||
20 | * Author: Paul E. McKenney <paulmck@us.ibm.com> | ||
21 | * Based on kernel/rcu/torture.c. | ||
22 | */ | ||
23 | #include <linux/types.h> | ||
24 | #include <linux/kernel.h> | ||
25 | #include <linux/init.h> | ||
26 | #include <linux/module.h> | ||
27 | #include <linux/kthread.h> | ||
28 | #include <linux/err.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <linux/smp.h> | ||
31 | #include <linux/interrupt.h> | ||
32 | #include <linux/sched.h> | ||
33 | #include <linux/atomic.h> | ||
34 | #include <linux/bitops.h> | ||
35 | #include <linux/completion.h> | ||
36 | #include <linux/moduleparam.h> | ||
37 | #include <linux/percpu.h> | ||
38 | #include <linux/notifier.h> | ||
39 | #include <linux/reboot.h> | ||
40 | #include <linux/freezer.h> | ||
41 | #include <linux/cpu.h> | ||
42 | #include <linux/delay.h> | ||
43 | #include <linux/stat.h> | ||
44 | #include <linux/slab.h> | ||
45 | #include <linux/trace_clock.h> | ||
46 | #include <asm/byteorder.h> | ||
47 | #include <linux/torture.h> | ||
48 | |||
49 | MODULE_LICENSE("GPL"); | ||
50 | MODULE_AUTHOR("Paul E. McKenney <paulmck@us.ibm.com>"); | ||
51 | |||
52 | torture_param(int, nwriters_stress, -1, | ||
53 | "Number of write-locking stress-test threads"); | ||
54 | torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)"); | ||
55 | torture_param(int, onoff_interval, 0, | ||
56 | "Time between CPU hotplugs (s), 0=disable"); | ||
57 | torture_param(int, shuffle_interval, 3, | ||
58 | "Number of jiffies between shuffles, 0=disable"); | ||
59 | torture_param(int, shutdown_secs, 0, "Shutdown time (j), <= zero to disable."); | ||
60 | torture_param(int, stat_interval, 60, | ||
61 | "Number of seconds between stats printk()s"); | ||
62 | torture_param(int, stutter, 5, "Number of jiffies to run/halt test, 0=disable"); | ||
63 | torture_param(bool, verbose, true, | ||
64 | "Enable verbose debugging printk()s"); | ||
65 | |||
66 | static char *torture_type = "spin_lock"; | ||
67 | module_param(torture_type, charp, 0444); | ||
68 | MODULE_PARM_DESC(torture_type, | ||
69 | "Type of lock to torture (spin_lock, spin_lock_irq, ...)"); | ||
70 | |||
71 | static atomic_t n_lock_torture_errors; | ||
72 | |||
73 | static struct task_struct *stats_task; | ||
74 | static struct task_struct **writer_tasks; | ||
75 | |||
76 | static int nrealwriters_stress; | ||
77 | static bool lock_is_write_held; | ||
78 | |||
79 | struct lock_writer_stress_stats { | ||
80 | long n_write_lock_fail; | ||
81 | long n_write_lock_acquired; | ||
82 | }; | ||
83 | static struct lock_writer_stress_stats *lwsa; | ||
84 | |||
85 | #if defined(MODULE) || defined(CONFIG_LOCK_TORTURE_TEST_RUNNABLE) | ||
86 | #define LOCKTORTURE_RUNNABLE_INIT 1 | ||
87 | #else | ||
88 | #define LOCKTORTURE_RUNNABLE_INIT 0 | ||
89 | #endif | ||
90 | int locktorture_runnable = LOCKTORTURE_RUNNABLE_INIT; | ||
91 | module_param(locktorture_runnable, int, 0444); | ||
92 | MODULE_PARM_DESC(locktorture_runnable, "Start locktorture at boot"); | ||
93 | |||
94 | /* Forward reference. */ | ||
95 | static void lock_torture_cleanup(void); | ||
96 | |||
97 | /* | ||
98 | * Operations vector for selecting different types of tests. | ||
99 | */ | ||
100 | struct lock_torture_ops { | ||
101 | void (*init)(void); | ||
102 | int (*writelock)(void); | ||
103 | void (*write_delay)(struct torture_random_state *trsp); | ||
104 | void (*writeunlock)(void); | ||
105 | unsigned long flags; | ||
106 | const char *name; | ||
107 | }; | ||
108 | |||
109 | static struct lock_torture_ops *cur_ops; | ||
110 | |||
111 | /* | ||
112 | * Definitions for lock torture testing. | ||
113 | */ | ||
114 | |||
115 | static DEFINE_SPINLOCK(torture_spinlock); | ||
116 | |||
117 | static int torture_spin_lock_write_lock(void) __acquires(torture_spinlock) | ||
118 | { | ||
119 | spin_lock(&torture_spinlock); | ||
120 | return 0; | ||
121 | } | ||
122 | |||
123 | static void torture_spin_lock_write_delay(struct torture_random_state *trsp) | ||
124 | { | ||
125 | const unsigned long shortdelay_us = 2; | ||
126 | const unsigned long longdelay_us = 100; | ||
127 | |||
128 | /* We want a short delay mostly to emulate likely code, and | ||
129 | * we want a long delay occasionally to force massive contention. | ||
130 | */ | ||
131 | if (!(torture_random(trsp) % | ||
132 | (nrealwriters_stress * 2000 * longdelay_us))) | ||
133 | mdelay(longdelay_us); | ||
134 | if (!(torture_random(trsp) % | ||
135 | (nrealwriters_stress * 2 * shortdelay_us))) | ||
136 | udelay(shortdelay_us); | ||
137 | #ifdef CONFIG_PREEMPT | ||
138 | if (!(torture_random(trsp) % (nrealwriters_stress * 20000))) | ||
139 | preempt_schedule(); /* Allow test to be preempted. */ | ||
140 | #endif | ||
141 | } | ||
142 | |||
143 | static void torture_spin_lock_write_unlock(void) __releases(torture_spinlock) | ||
144 | { | ||
145 | spin_unlock(&torture_spinlock); | ||
146 | } | ||
147 | |||
148 | static struct lock_torture_ops spin_lock_ops = { | ||
149 | .writelock = torture_spin_lock_write_lock, | ||
150 | .write_delay = torture_spin_lock_write_delay, | ||
151 | .writeunlock = torture_spin_lock_write_unlock, | ||
152 | .name = "spin_lock" | ||
153 | }; | ||
154 | |||
155 | static int torture_spin_lock_write_lock_irq(void) | ||
156 | __acquires(torture_spinlock_irq) | ||
157 | { | ||
158 | unsigned long flags; | ||
159 | |||
160 | spin_lock_irqsave(&torture_spinlock, flags); | ||
161 | cur_ops->flags = flags; | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static void torture_lock_spin_write_unlock_irq(void) | ||
166 | __releases(torture_spinlock) | ||
167 | { | ||
168 | spin_unlock_irqrestore(&torture_spinlock, cur_ops->flags); | ||
169 | } | ||
170 | |||
171 | static struct lock_torture_ops spin_lock_irq_ops = { | ||
172 | .writelock = torture_spin_lock_write_lock_irq, | ||
173 | .write_delay = torture_spin_lock_write_delay, | ||
174 | .writeunlock = torture_lock_spin_write_unlock_irq, | ||
175 | .name = "spin_lock_irq" | ||
176 | }; | ||
177 | |||
178 | /* | ||
179 | * Lock torture writer kthread. Repeatedly acquires and releases | ||
180 | * the lock, checking for duplicate acquisitions. | ||
181 | */ | ||
182 | static int lock_torture_writer(void *arg) | ||
183 | { | ||
184 | struct lock_writer_stress_stats *lwsp = arg; | ||
185 | static DEFINE_TORTURE_RANDOM(rand); | ||
186 | |||
187 | VERBOSE_TOROUT_STRING("lock_torture_writer task started"); | ||
188 | set_user_nice(current, 19); | ||
189 | |||
190 | do { | ||
191 | schedule_timeout_uninterruptible(1); | ||
192 | cur_ops->writelock(); | ||
193 | if (WARN_ON_ONCE(lock_is_write_held)) | ||
194 | lwsp->n_write_lock_fail++; | ||
195 | lock_is_write_held = 1; | ||
196 | lwsp->n_write_lock_acquired++; | ||
197 | cur_ops->write_delay(&rand); | ||
198 | lock_is_write_held = 0; | ||
199 | cur_ops->writeunlock(); | ||
200 | stutter_wait("lock_torture_writer"); | ||
201 | } while (!torture_must_stop()); | ||
202 | torture_kthread_stopping("lock_torture_writer"); | ||
203 | return 0; | ||
204 | } | ||
205 | |||
206 | /* | ||
207 | * Create an lock-torture-statistics message in the specified buffer. | ||
208 | */ | ||
209 | static void lock_torture_printk(char *page) | ||
210 | { | ||
211 | bool fail = 0; | ||
212 | int i; | ||
213 | long max = 0; | ||
214 | long min = lwsa[0].n_write_lock_acquired; | ||
215 | long long sum = 0; | ||
216 | |||
217 | for (i = 0; i < nrealwriters_stress; i++) { | ||
218 | if (lwsa[i].n_write_lock_fail) | ||
219 | fail = true; | ||
220 | sum += lwsa[i].n_write_lock_acquired; | ||
221 | if (max < lwsa[i].n_write_lock_fail) | ||
222 | max = lwsa[i].n_write_lock_fail; | ||
223 | if (min > lwsa[i].n_write_lock_fail) | ||
224 | min = lwsa[i].n_write_lock_fail; | ||
225 | } | ||
226 | page += sprintf(page, "%s%s ", torture_type, TORTURE_FLAG); | ||
227 | page += sprintf(page, | ||
228 | "Writes: Total: %lld Max/Min: %ld/%ld %s Fail: %d %s\n", | ||
229 | sum, max, min, max / 2 > min ? "???" : "", | ||
230 | fail, fail ? "!!!" : ""); | ||
231 | if (fail) | ||
232 | atomic_inc(&n_lock_torture_errors); | ||
233 | } | ||
234 | |||
235 | /* | ||
236 | * Print torture statistics. Caller must ensure that there is only one | ||
237 | * call to this function at a given time!!! This is normally accomplished | ||
238 | * by relying on the module system to only have one copy of the module | ||
239 | * loaded, and then by giving the lock_torture_stats kthread full control | ||
240 | * (or the init/cleanup functions when lock_torture_stats thread is not | ||
241 | * running). | ||
242 | */ | ||
243 | static void lock_torture_stats_print(void) | ||
244 | { | ||
245 | int size = nrealwriters_stress * 200 + 8192; | ||
246 | char *buf; | ||
247 | |||
248 | buf = kmalloc(size, GFP_KERNEL); | ||
249 | if (!buf) { | ||
250 | pr_err("lock_torture_stats_print: Out of memory, need: %d", | ||
251 | size); | ||
252 | return; | ||
253 | } | ||
254 | lock_torture_printk(buf); | ||
255 | pr_alert("%s", buf); | ||
256 | kfree(buf); | ||
257 | } | ||
258 | |||
259 | /* | ||
260 | * Periodically prints torture statistics, if periodic statistics printing | ||
261 | * was specified via the stat_interval module parameter. | ||
262 | * | ||
263 | * No need to worry about fullstop here, since this one doesn't reference | ||
264 | * volatile state or register callbacks. | ||
265 | */ | ||
266 | static int lock_torture_stats(void *arg) | ||
267 | { | ||
268 | VERBOSE_TOROUT_STRING("lock_torture_stats task started"); | ||
269 | do { | ||
270 | schedule_timeout_interruptible(stat_interval * HZ); | ||
271 | lock_torture_stats_print(); | ||
272 | torture_shutdown_absorb("lock_torture_stats"); | ||
273 | } while (!torture_must_stop()); | ||
274 | torture_kthread_stopping("lock_torture_stats"); | ||
275 | return 0; | ||
276 | } | ||
277 | |||
278 | static inline void | ||
279 | lock_torture_print_module_parms(struct lock_torture_ops *cur_ops, | ||
280 | const char *tag) | ||
281 | { | ||
282 | pr_alert("%s" TORTURE_FLAG | ||
283 | "--- %s: nwriters_stress=%d stat_interval=%d verbose=%d shuffle_interval=%d stutter=%d shutdown_secs=%d onoff_interval=%d onoff_holdoff=%d\n", | ||
284 | torture_type, tag, nrealwriters_stress, stat_interval, verbose, | ||
285 | shuffle_interval, stutter, shutdown_secs, | ||
286 | onoff_interval, onoff_holdoff); | ||
287 | } | ||
288 | |||
289 | static void lock_torture_cleanup(void) | ||
290 | { | ||
291 | int i; | ||
292 | |||
293 | if (torture_cleanup()) | ||
294 | return; | ||
295 | |||
296 | if (writer_tasks) { | ||
297 | for (i = 0; i < nrealwriters_stress; i++) | ||
298 | torture_stop_kthread(lock_torture_writer, | ||
299 | writer_tasks[i]); | ||
300 | kfree(writer_tasks); | ||
301 | writer_tasks = NULL; | ||
302 | } | ||
303 | |||
304 | torture_stop_kthread(lock_torture_stats, stats_task); | ||
305 | lock_torture_stats_print(); /* -After- the stats thread is stopped! */ | ||
306 | |||
307 | if (atomic_read(&n_lock_torture_errors)) | ||
308 | lock_torture_print_module_parms(cur_ops, | ||
309 | "End of test: FAILURE"); | ||
310 | else if (torture_onoff_failures()) | ||
311 | lock_torture_print_module_parms(cur_ops, | ||
312 | "End of test: LOCK_HOTPLUG"); | ||
313 | else | ||
314 | lock_torture_print_module_parms(cur_ops, | ||
315 | "End of test: SUCCESS"); | ||
316 | } | ||
317 | |||
318 | static int __init lock_torture_init(void) | ||
319 | { | ||
320 | int i; | ||
321 | int firsterr = 0; | ||
322 | static struct lock_torture_ops *torture_ops[] = { | ||
323 | &spin_lock_ops, &spin_lock_irq_ops, | ||
324 | }; | ||
325 | |||
326 | torture_init_begin(torture_type, verbose, &locktorture_runnable); | ||
327 | |||
328 | /* Process args and tell the world that the torturer is on the job. */ | ||
329 | for (i = 0; i < ARRAY_SIZE(torture_ops); i++) { | ||
330 | cur_ops = torture_ops[i]; | ||
331 | if (strcmp(torture_type, cur_ops->name) == 0) | ||
332 | break; | ||
333 | } | ||
334 | if (i == ARRAY_SIZE(torture_ops)) { | ||
335 | pr_alert("lock-torture: invalid torture type: \"%s\"\n", | ||
336 | torture_type); | ||
337 | pr_alert("lock-torture types:"); | ||
338 | for (i = 0; i < ARRAY_SIZE(torture_ops); i++) | ||
339 | pr_alert(" %s", torture_ops[i]->name); | ||
340 | pr_alert("\n"); | ||
341 | torture_init_end(); | ||
342 | return -EINVAL; | ||
343 | } | ||
344 | if (cur_ops->init) | ||
345 | cur_ops->init(); /* no "goto unwind" prior to this point!!! */ | ||
346 | |||
347 | if (nwriters_stress >= 0) | ||
348 | nrealwriters_stress = nwriters_stress; | ||
349 | else | ||
350 | nrealwriters_stress = 2 * num_online_cpus(); | ||
351 | lock_torture_print_module_parms(cur_ops, "Start of test"); | ||
352 | |||
353 | /* Initialize the statistics so that each run gets its own numbers. */ | ||
354 | |||
355 | lock_is_write_held = 0; | ||
356 | lwsa = kmalloc(sizeof(*lwsa) * nrealwriters_stress, GFP_KERNEL); | ||
357 | if (lwsa == NULL) { | ||
358 | VERBOSE_TOROUT_STRING("lwsa: Out of memory"); | ||
359 | firsterr = -ENOMEM; | ||
360 | goto unwind; | ||
361 | } | ||
362 | for (i = 0; i < nrealwriters_stress; i++) { | ||
363 | lwsa[i].n_write_lock_fail = 0; | ||
364 | lwsa[i].n_write_lock_acquired = 0; | ||
365 | } | ||
366 | |||
367 | /* Start up the kthreads. */ | ||
368 | |||
369 | if (onoff_interval > 0) { | ||
370 | firsterr = torture_onoff_init(onoff_holdoff * HZ, | ||
371 | onoff_interval * HZ); | ||
372 | if (firsterr) | ||
373 | goto unwind; | ||
374 | } | ||
375 | if (shuffle_interval > 0) { | ||
376 | firsterr = torture_shuffle_init(shuffle_interval); | ||
377 | if (firsterr) | ||
378 | goto unwind; | ||
379 | } | ||
380 | if (shutdown_secs > 0) { | ||
381 | firsterr = torture_shutdown_init(shutdown_secs, | ||
382 | lock_torture_cleanup); | ||
383 | if (firsterr) | ||
384 | goto unwind; | ||
385 | } | ||
386 | if (stutter > 0) { | ||
387 | firsterr = torture_stutter_init(stutter); | ||
388 | if (firsterr) | ||
389 | goto unwind; | ||
390 | } | ||
391 | |||
392 | writer_tasks = kzalloc(nrealwriters_stress * sizeof(writer_tasks[0]), | ||
393 | GFP_KERNEL); | ||
394 | if (writer_tasks == NULL) { | ||
395 | VERBOSE_TOROUT_ERRSTRING("writer_tasks: Out of memory"); | ||
396 | firsterr = -ENOMEM; | ||
397 | goto unwind; | ||
398 | } | ||
399 | for (i = 0; i < nrealwriters_stress; i++) { | ||
400 | firsterr = torture_create_kthread(lock_torture_writer, &lwsa[i], | ||
401 | writer_tasks[i]); | ||
402 | if (firsterr) | ||
403 | goto unwind; | ||
404 | } | ||
405 | if (stat_interval > 0) { | ||
406 | firsterr = torture_create_kthread(lock_torture_stats, NULL, | ||
407 | stats_task); | ||
408 | if (firsterr) | ||
409 | goto unwind; | ||
410 | } | ||
411 | torture_init_end(); | ||
412 | return 0; | ||
413 | |||
414 | unwind: | ||
415 | torture_init_end(); | ||
416 | lock_torture_cleanup(); | ||
417 | return firsterr; | ||
418 | } | ||
419 | |||
420 | module_init(lock_torture_init); | ||
421 | module_exit(lock_torture_cleanup); | ||