diff options
Diffstat (limited to 'kernel/rcutorture.c')
-rw-r--r-- | kernel/rcutorture.c | 133 |
1 files changed, 123 insertions, 10 deletions
diff --git a/kernel/rcutorture.c b/kernel/rcutorture.c index 23446e91cded..e34d22bf2293 100644 --- a/kernel/rcutorture.c +++ b/kernel/rcutorture.c | |||
@@ -44,6 +44,7 @@ | |||
44 | #include <linux/delay.h> | 44 | #include <linux/delay.h> |
45 | #include <linux/byteorder/swabb.h> | 45 | #include <linux/byteorder/swabb.h> |
46 | #include <linux/stat.h> | 46 | #include <linux/stat.h> |
47 | #include <linux/srcu.h> | ||
47 | 48 | ||
48 | MODULE_LICENSE("GPL"); | 49 | MODULE_LICENSE("GPL"); |
49 | 50 | ||
@@ -53,7 +54,7 @@ static int stat_interval; /* Interval between stats, in seconds. */ | |||
53 | static int verbose; /* Print more debug info. */ | 54 | static int verbose; /* Print more debug info. */ |
54 | static int test_no_idle_hz; /* Test RCU's support for tickless idle CPUs. */ | 55 | static int test_no_idle_hz; /* Test RCU's support for tickless idle CPUs. */ |
55 | static int shuffle_interval = 5; /* Interval between shuffles (in sec)*/ | 56 | static int shuffle_interval = 5; /* Interval between shuffles (in sec)*/ |
56 | static char *torture_type = "rcu"; /* What to torture. */ | 57 | static char *torture_type = "rcu"; /* What to torture: rcu, srcu. */ |
57 | 58 | ||
58 | module_param(nreaders, int, 0); | 59 | module_param(nreaders, int, 0); |
59 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); | 60 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); |
@@ -66,7 +67,7 @@ MODULE_PARM_DESC(test_no_idle_hz, "Test support for tickless idle CPUs"); | |||
66 | module_param(shuffle_interval, int, 0); | 67 | module_param(shuffle_interval, int, 0); |
67 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); | 68 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); |
68 | module_param(torture_type, charp, 0); | 69 | module_param(torture_type, charp, 0); |
69 | MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu, rcu_bh)"); | 70 | MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu, rcu_bh, srcu)"); |
70 | 71 | ||
71 | #define TORTURE_FLAG "-torture:" | 72 | #define TORTURE_FLAG "-torture:" |
72 | #define PRINTK_STRING(s) \ | 73 | #define PRINTK_STRING(s) \ |
@@ -104,11 +105,11 @@ static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1], rcu_torture_count) = | |||
104 | static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1], rcu_torture_batch) = | 105 | static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1], rcu_torture_batch) = |
105 | { 0 }; | 106 | { 0 }; |
106 | static atomic_t rcu_torture_wcount[RCU_TORTURE_PIPE_LEN + 1]; | 107 | static atomic_t rcu_torture_wcount[RCU_TORTURE_PIPE_LEN + 1]; |
107 | atomic_t n_rcu_torture_alloc; | 108 | static atomic_t n_rcu_torture_alloc; |
108 | atomic_t n_rcu_torture_alloc_fail; | 109 | static atomic_t n_rcu_torture_alloc_fail; |
109 | atomic_t n_rcu_torture_free; | 110 | static atomic_t n_rcu_torture_free; |
110 | atomic_t n_rcu_torture_mberror; | 111 | static atomic_t n_rcu_torture_mberror; |
111 | atomic_t n_rcu_torture_error; | 112 | static atomic_t n_rcu_torture_error; |
112 | 113 | ||
113 | /* | 114 | /* |
114 | * Allocate an element from the rcu_tortures pool. | 115 | * Allocate an element from the rcu_tortures pool. |
@@ -180,6 +181,7 @@ struct rcu_torture_ops { | |||
180 | void (*init)(void); | 181 | void (*init)(void); |
181 | void (*cleanup)(void); | 182 | void (*cleanup)(void); |
182 | int (*readlock)(void); | 183 | int (*readlock)(void); |
184 | void (*readdelay)(struct rcu_random_state *rrsp); | ||
183 | void (*readunlock)(int idx); | 185 | void (*readunlock)(int idx); |
184 | int (*completed)(void); | 186 | int (*completed)(void); |
185 | void (*deferredfree)(struct rcu_torture *p); | 187 | void (*deferredfree)(struct rcu_torture *p); |
@@ -198,6 +200,18 @@ static int rcu_torture_read_lock(void) __acquires(RCU) | |||
198 | return 0; | 200 | return 0; |
199 | } | 201 | } |
200 | 202 | ||
203 | static void rcu_read_delay(struct rcu_random_state *rrsp) | ||
204 | { | ||
205 | long delay; | ||
206 | const long longdelay = 200; | ||
207 | |||
208 | /* We want there to be long-running readers, but not all the time. */ | ||
209 | |||
210 | delay = rcu_random(rrsp) % (nrealreaders * 2 * longdelay); | ||
211 | if (!delay) | ||
212 | udelay(longdelay); | ||
213 | } | ||
214 | |||
201 | static void rcu_torture_read_unlock(int idx) __releases(RCU) | 215 | static void rcu_torture_read_unlock(int idx) __releases(RCU) |
202 | { | 216 | { |
203 | rcu_read_unlock(); | 217 | rcu_read_unlock(); |
@@ -239,6 +253,7 @@ static struct rcu_torture_ops rcu_ops = { | |||
239 | .init = NULL, | 253 | .init = NULL, |
240 | .cleanup = NULL, | 254 | .cleanup = NULL, |
241 | .readlock = rcu_torture_read_lock, | 255 | .readlock = rcu_torture_read_lock, |
256 | .readdelay = rcu_read_delay, | ||
242 | .readunlock = rcu_torture_read_unlock, | 257 | .readunlock = rcu_torture_read_unlock, |
243 | .completed = rcu_torture_completed, | 258 | .completed = rcu_torture_completed, |
244 | .deferredfree = rcu_torture_deferred_free, | 259 | .deferredfree = rcu_torture_deferred_free, |
@@ -275,6 +290,7 @@ static struct rcu_torture_ops rcu_bh_ops = { | |||
275 | .init = NULL, | 290 | .init = NULL, |
276 | .cleanup = NULL, | 291 | .cleanup = NULL, |
277 | .readlock = rcu_bh_torture_read_lock, | 292 | .readlock = rcu_bh_torture_read_lock, |
293 | .readdelay = rcu_read_delay, /* just reuse rcu's version. */ | ||
278 | .readunlock = rcu_bh_torture_read_unlock, | 294 | .readunlock = rcu_bh_torture_read_unlock, |
279 | .completed = rcu_bh_torture_completed, | 295 | .completed = rcu_bh_torture_completed, |
280 | .deferredfree = rcu_bh_torture_deferred_free, | 296 | .deferredfree = rcu_bh_torture_deferred_free, |
@@ -282,8 +298,105 @@ static struct rcu_torture_ops rcu_bh_ops = { | |||
282 | .name = "rcu_bh" | 298 | .name = "rcu_bh" |
283 | }; | 299 | }; |
284 | 300 | ||
301 | /* | ||
302 | * Definitions for srcu torture testing. | ||
303 | */ | ||
304 | |||
305 | static struct srcu_struct srcu_ctl; | ||
306 | static struct list_head srcu_removed; | ||
307 | |||
308 | static void srcu_torture_init(void) | ||
309 | { | ||
310 | init_srcu_struct(&srcu_ctl); | ||
311 | INIT_LIST_HEAD(&srcu_removed); | ||
312 | } | ||
313 | |||
314 | static void srcu_torture_cleanup(void) | ||
315 | { | ||
316 | synchronize_srcu(&srcu_ctl); | ||
317 | cleanup_srcu_struct(&srcu_ctl); | ||
318 | } | ||
319 | |||
320 | static int srcu_torture_read_lock(void) | ||
321 | { | ||
322 | return srcu_read_lock(&srcu_ctl); | ||
323 | } | ||
324 | |||
325 | static void srcu_read_delay(struct rcu_random_state *rrsp) | ||
326 | { | ||
327 | long delay; | ||
328 | const long uspertick = 1000000 / HZ; | ||
329 | const long longdelay = 10; | ||
330 | |||
331 | /* We want there to be long-running readers, but not all the time. */ | ||
332 | |||
333 | delay = rcu_random(rrsp) % (nrealreaders * 2 * longdelay * uspertick); | ||
334 | if (!delay) | ||
335 | schedule_timeout_interruptible(longdelay); | ||
336 | } | ||
337 | |||
338 | static void srcu_torture_read_unlock(int idx) | ||
339 | { | ||
340 | srcu_read_unlock(&srcu_ctl, idx); | ||
341 | } | ||
342 | |||
343 | static int srcu_torture_completed(void) | ||
344 | { | ||
345 | return srcu_batches_completed(&srcu_ctl); | ||
346 | } | ||
347 | |||
348 | static void srcu_torture_deferred_free(struct rcu_torture *p) | ||
349 | { | ||
350 | int i; | ||
351 | struct rcu_torture *rp; | ||
352 | struct rcu_torture *rp1; | ||
353 | |||
354 | synchronize_srcu(&srcu_ctl); | ||
355 | list_add(&p->rtort_free, &srcu_removed); | ||
356 | list_for_each_entry_safe(rp, rp1, &srcu_removed, rtort_free) { | ||
357 | i = rp->rtort_pipe_count; | ||
358 | if (i > RCU_TORTURE_PIPE_LEN) | ||
359 | i = RCU_TORTURE_PIPE_LEN; | ||
360 | atomic_inc(&rcu_torture_wcount[i]); | ||
361 | if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) { | ||
362 | rp->rtort_mbtest = 0; | ||
363 | list_del(&rp->rtort_free); | ||
364 | rcu_torture_free(rp); | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | |||
369 | static int srcu_torture_stats(char *page) | ||
370 | { | ||
371 | int cnt = 0; | ||
372 | int cpu; | ||
373 | int idx = srcu_ctl.completed & 0x1; | ||
374 | |||
375 | cnt += sprintf(&page[cnt], "%s%s per-CPU(idx=%d):", | ||
376 | torture_type, TORTURE_FLAG, idx); | ||
377 | for_each_possible_cpu(cpu) { | ||
378 | cnt += sprintf(&page[cnt], " %d(%d,%d)", cpu, | ||
379 | per_cpu_ptr(srcu_ctl.per_cpu_ref, cpu)->c[!idx], | ||
380 | per_cpu_ptr(srcu_ctl.per_cpu_ref, cpu)->c[idx]); | ||
381 | } | ||
382 | cnt += sprintf(&page[cnt], "\n"); | ||
383 | return cnt; | ||
384 | } | ||
385 | |||
386 | static struct rcu_torture_ops srcu_ops = { | ||
387 | .init = srcu_torture_init, | ||
388 | .cleanup = srcu_torture_cleanup, | ||
389 | .readlock = srcu_torture_read_lock, | ||
390 | .readdelay = srcu_read_delay, | ||
391 | .readunlock = srcu_torture_read_unlock, | ||
392 | .completed = srcu_torture_completed, | ||
393 | .deferredfree = srcu_torture_deferred_free, | ||
394 | .stats = srcu_torture_stats, | ||
395 | .name = "srcu" | ||
396 | }; | ||
397 | |||
285 | static struct rcu_torture_ops *torture_ops[] = | 398 | static struct rcu_torture_ops *torture_ops[] = |
286 | { &rcu_ops, &rcu_bh_ops, NULL }; | 399 | { &rcu_ops, &rcu_bh_ops, &srcu_ops, NULL }; |
287 | 400 | ||
288 | /* | 401 | /* |
289 | * RCU torture writer kthread. Repeatedly substitutes a new structure | 402 | * RCU torture writer kthread. Repeatedly substitutes a new structure |
@@ -359,7 +472,7 @@ rcu_torture_reader(void *arg) | |||
359 | } | 472 | } |
360 | if (p->rtort_mbtest == 0) | 473 | if (p->rtort_mbtest == 0) |
361 | atomic_inc(&n_rcu_torture_mberror); | 474 | atomic_inc(&n_rcu_torture_mberror); |
362 | udelay(rcu_random(&rand) & 0x7f); | 475 | cur_ops->readdelay(&rand); |
363 | preempt_disable(); | 476 | preempt_disable(); |
364 | pipe_count = p->rtort_pipe_count; | 477 | pipe_count = p->rtort_pipe_count; |
365 | if (pipe_count > RCU_TORTURE_PIPE_LEN) { | 478 | if (pipe_count > RCU_TORTURE_PIPE_LEN) { |
@@ -483,7 +596,7 @@ static int rcu_idle_cpu; /* Force all torture tasks off this CPU */ | |||
483 | /* Shuffle tasks such that we allow @rcu_idle_cpu to become idle. A special case | 596 | /* Shuffle tasks such that we allow @rcu_idle_cpu to become idle. A special case |
484 | * is when @rcu_idle_cpu = -1, when we allow the tasks to run on all CPUs. | 597 | * is when @rcu_idle_cpu = -1, when we allow the tasks to run on all CPUs. |
485 | */ | 598 | */ |
486 | void rcu_torture_shuffle_tasks(void) | 599 | static void rcu_torture_shuffle_tasks(void) |
487 | { | 600 | { |
488 | cpumask_t tmp_mask = CPU_MASK_ALL; | 601 | cpumask_t tmp_mask = CPU_MASK_ALL; |
489 | int i; | 602 | int i; |