diff options
Diffstat (limited to 'kernel/rcutorture.c')
| -rw-r--r-- | kernel/rcutorture.c | 201 | 
1 files changed, 157 insertions, 44 deletions
diff --git a/kernel/rcutorture.c b/kernel/rcutorture.c index 8154e7589d12..4f2c4272d59c 100644 --- a/kernel/rcutorture.c +++ b/kernel/rcutorture.c  | |||
| @@ -1,5 +1,5 @@ | |||
| 1 | /* | 1 | /* | 
| 2 | * Read-Copy Update /proc-based torture test facility | 2 | * Read-Copy Update module-based torture test facility | 
| 3 | * | 3 | * | 
| 4 | * This program is free software; you can redistribute it and/or modify | 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 | 5 | * it under the terms of the GNU General Public License as published by | 
| @@ -53,6 +53,7 @@ static int stat_interval; /* Interval between stats, in seconds. */ | |||
| 53 | static int verbose; /* Print more debug info. */ | 53 | static int verbose; /* Print more debug info. */ | 
| 54 | static int test_no_idle_hz; /* Test RCU's support for tickless idle CPUs. */ | 54 | 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)*/ | 55 | static int shuffle_interval = 5; /* Interval between shuffles (in sec)*/ | 
| 56 | static char *torture_type = "rcu"; /* What to torture. */ | ||
| 56 | 57 | ||
| 57 | module_param(nreaders, int, 0); | 58 | module_param(nreaders, int, 0); | 
| 58 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); | 59 | MODULE_PARM_DESC(nreaders, "Number of RCU reader threads"); | 
| @@ -64,13 +65,16 @@ module_param(test_no_idle_hz, bool, 0); | |||
| 64 | MODULE_PARM_DESC(test_no_idle_hz, "Test support for tickless idle CPUs"); | 65 | MODULE_PARM_DESC(test_no_idle_hz, "Test support for tickless idle CPUs"); | 
| 65 | module_param(shuffle_interval, int, 0); | 66 | module_param(shuffle_interval, int, 0); | 
| 66 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); | 67 | MODULE_PARM_DESC(shuffle_interval, "Number of seconds between shuffles"); | 
| 67 | #define TORTURE_FLAG "rcutorture: " | 68 | module_param(torture_type, charp, 0); | 
| 69 | MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu, rcu_bh)"); | ||
| 70 | |||
| 71 | #define TORTURE_FLAG "-torture:" | ||
| 68 | #define PRINTK_STRING(s) \ | 72 | #define PRINTK_STRING(s) \ | 
| 69 | do { printk(KERN_ALERT TORTURE_FLAG s "\n"); } while (0) | 73 | do { printk(KERN_ALERT "%s" TORTURE_FLAG s "\n", torture_type); } while (0) | 
| 70 | #define VERBOSE_PRINTK_STRING(s) \ | 74 | #define VERBOSE_PRINTK_STRING(s) \ | 
| 71 | do { if (verbose) printk(KERN_ALERT TORTURE_FLAG s "\n"); } while (0) | 75 | do { if (verbose) printk(KERN_ALERT "%s" TORTURE_FLAG s "\n", torture_type); } while (0) | 
| 72 | #define VERBOSE_PRINTK_ERRSTRING(s) \ | 76 | #define VERBOSE_PRINTK_ERRSTRING(s) \ | 
| 73 | do { if (verbose) printk(KERN_ALERT TORTURE_FLAG "!!! " s "\n"); } while (0) | 77 | do { if (verbose) printk(KERN_ALERT "%s" TORTURE_FLAG "!!! " s "\n", torture_type); } while (0) | 
| 74 | 78 | ||
| 75 | static char printk_buf[4096]; | 79 | static char printk_buf[4096]; | 
| 76 | 80 | ||
| @@ -139,28 +143,6 @@ rcu_torture_free(struct rcu_torture *p) | |||
| 139 | spin_unlock_bh(&rcu_torture_lock); | 143 | spin_unlock_bh(&rcu_torture_lock); | 
| 140 | } | 144 | } | 
| 141 | 145 | ||
| 142 | static void | ||
| 143 | rcu_torture_cb(struct rcu_head *p) | ||
| 144 | { | ||
| 145 | int i; | ||
| 146 | struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu); | ||
| 147 | |||
| 148 | if (fullstop) { | ||
| 149 | /* Test is ending, just drop callbacks on the floor. */ | ||
| 150 | /* The next initialization will pick up the pieces. */ | ||
| 151 | return; | ||
| 152 | } | ||
| 153 | i = rp->rtort_pipe_count; | ||
| 154 | if (i > RCU_TORTURE_PIPE_LEN) | ||
| 155 | i = RCU_TORTURE_PIPE_LEN; | ||
| 156 | atomic_inc(&rcu_torture_wcount[i]); | ||
| 157 | if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) { | ||
| 158 | rp->rtort_mbtest = 0; | ||
| 159 | rcu_torture_free(rp); | ||
| 160 | } else | ||
| 161 | call_rcu(p, rcu_torture_cb); | ||
| 162 | } | ||
| 163 | |||
| 164 | struct rcu_random_state { | 146 | struct rcu_random_state { | 
| 165 | unsigned long rrs_state; | 147 | unsigned long rrs_state; | 
| 166 | unsigned long rrs_count; | 148 | unsigned long rrs_count; | 
| @@ -191,6 +173,119 @@ rcu_random(struct rcu_random_state *rrsp) | |||
| 191 | } | 173 | } | 
| 192 | 174 | ||
| 193 | /* | 175 | /* | 
| 176 | * Operations vector for selecting different types of tests. | ||
| 177 | */ | ||
| 178 | |||
| 179 | struct rcu_torture_ops { | ||
| 180 | void (*init)(void); | ||
| 181 | void (*cleanup)(void); | ||
| 182 | int (*readlock)(void); | ||
| 183 | void (*readunlock)(int idx); | ||
| 184 | int (*completed)(void); | ||
| 185 | void (*deferredfree)(struct rcu_torture *p); | ||
| 186 | int (*stats)(char *page); | ||
| 187 | char *name; | ||
| 188 | }; | ||
| 189 | static struct rcu_torture_ops *cur_ops = NULL; | ||
| 190 | |||
| 191 | /* | ||
| 192 | * Definitions for rcu torture testing. | ||
| 193 | */ | ||
| 194 | |||
| 195 | static int rcu_torture_read_lock(void) __acquires(RCU) | ||
| 196 | { | ||
| 197 | rcu_read_lock(); | ||
| 198 | return 0; | ||
| 199 | } | ||
| 200 | |||
| 201 | static void rcu_torture_read_unlock(int idx) __releases(RCU) | ||
| 202 | { | ||
| 203 | rcu_read_unlock(); | ||
| 204 | } | ||
| 205 | |||
| 206 | static int rcu_torture_completed(void) | ||
| 207 | { | ||
| 208 | return rcu_batches_completed(); | ||
| 209 | } | ||
| 210 | |||
| 211 | static void | ||
| 212 | rcu_torture_cb(struct rcu_head *p) | ||
| 213 | { | ||
| 214 | int i; | ||
| 215 | struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu); | ||
| 216 | |||
| 217 | if (fullstop) { | ||
| 218 | /* Test is ending, just drop callbacks on the floor. */ | ||
| 219 | /* The next initialization will pick up the pieces. */ | ||
| 220 | return; | ||
| 221 | } | ||
| 222 | i = rp->rtort_pipe_count; | ||
| 223 | if (i > RCU_TORTURE_PIPE_LEN) | ||
| 224 | i = RCU_TORTURE_PIPE_LEN; | ||
| 225 | atomic_inc(&rcu_torture_wcount[i]); | ||
| 226 | if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) { | ||
| 227 | rp->rtort_mbtest = 0; | ||
| 228 | rcu_torture_free(rp); | ||
| 229 | } else | ||
| 230 | cur_ops->deferredfree(rp); | ||
| 231 | } | ||
| 232 | |||
| 233 | static void rcu_torture_deferred_free(struct rcu_torture *p) | ||
| 234 | { | ||
| 235 | call_rcu(&p->rtort_rcu, rcu_torture_cb); | ||
| 236 | } | ||
| 237 | |||
| 238 | static struct rcu_torture_ops rcu_ops = { | ||
| 239 | .init = NULL, | ||
| 240 | .cleanup = NULL, | ||
| 241 | .readlock = rcu_torture_read_lock, | ||
| 242 | .readunlock = rcu_torture_read_unlock, | ||
| 243 | .completed = rcu_torture_completed, | ||
| 244 | .deferredfree = rcu_torture_deferred_free, | ||
| 245 | .stats = NULL, | ||
| 246 | .name = "rcu" | ||
| 247 | }; | ||
| 248 | |||
| 249 | /* | ||
| 250 | * Definitions for rcu_bh torture testing. | ||
| 251 | */ | ||
| 252 | |||
| 253 | static int rcu_bh_torture_read_lock(void) __acquires(RCU_BH) | ||
| 254 | { | ||
| 255 | rcu_read_lock_bh(); | ||
| 256 | return 0; | ||
| 257 | } | ||
| 258 | |||
| 259 | static void rcu_bh_torture_read_unlock(int idx) __releases(RCU_BH) | ||
| 260 | { | ||
| 261 | rcu_read_unlock_bh(); | ||
| 262 | } | ||
| 263 | |||
| 264 | static int rcu_bh_torture_completed(void) | ||
| 265 | { | ||
| 266 | return rcu_batches_completed_bh(); | ||
| 267 | } | ||
| 268 | |||
| 269 | static void rcu_bh_torture_deferred_free(struct rcu_torture *p) | ||
| 270 | { | ||
| 271 | call_rcu_bh(&p->rtort_rcu, rcu_torture_cb); | ||
| 272 | } | ||
| 273 | |||
| 274 | static struct rcu_torture_ops rcu_bh_ops = { | ||
| 275 | .init = NULL, | ||
| 276 | .cleanup = NULL, | ||
| 277 | .readlock = rcu_bh_torture_read_lock, | ||
| 278 | .readunlock = rcu_bh_torture_read_unlock, | ||
| 279 | .completed = rcu_bh_torture_completed, | ||
| 280 | .deferredfree = rcu_bh_torture_deferred_free, | ||
| 281 | .stats = NULL, | ||
| 282 | .name = "rcu_bh" | ||
| 283 | }; | ||
| 284 | |||
| 285 | static struct rcu_torture_ops *torture_ops[] = | ||
| 286 | { &rcu_ops, &rcu_bh_ops, NULL }; | ||
| 287 | |||
| 288 | /* | ||
| 194 | * RCU torture writer kthread. Repeatedly substitutes a new structure | 289 | * RCU torture writer kthread. Repeatedly substitutes a new structure | 
| 195 | * for that pointed to by rcu_torture_current, freeing the old structure | 290 | * for that pointed to by rcu_torture_current, freeing the old structure | 
| 196 | * after a series of grace periods (the "pipeline"). | 291 | * after a series of grace periods (the "pipeline"). | 
| @@ -209,8 +304,6 @@ rcu_torture_writer(void *arg) | |||
| 209 | 304 | ||
| 210 | do { | 305 | do { | 
| 211 | schedule_timeout_uninterruptible(1); | 306 | schedule_timeout_uninterruptible(1); | 
| 212 | if (rcu_batches_completed() == oldbatch) | ||
| 213 | continue; | ||
| 214 | if ((rp = rcu_torture_alloc()) == NULL) | 307 | if ((rp = rcu_torture_alloc()) == NULL) | 
| 215 | continue; | 308 | continue; | 
| 216 | rp->rtort_pipe_count = 0; | 309 | rp->rtort_pipe_count = 0; | 
| @@ -225,10 +318,10 @@ rcu_torture_writer(void *arg) | |||
| 225 | i = RCU_TORTURE_PIPE_LEN; | 318 | i = RCU_TORTURE_PIPE_LEN; | 
| 226 | atomic_inc(&rcu_torture_wcount[i]); | 319 | atomic_inc(&rcu_torture_wcount[i]); | 
| 227 | old_rp->rtort_pipe_count++; | 320 | old_rp->rtort_pipe_count++; | 
| 228 | call_rcu(&old_rp->rtort_rcu, rcu_torture_cb); | 321 | cur_ops->deferredfree(old_rp); | 
| 229 | } | 322 | } | 
| 230 | rcu_torture_current_version++; | 323 | rcu_torture_current_version++; | 
| 231 | oldbatch = rcu_batches_completed(); | 324 | oldbatch = cur_ops->completed(); | 
| 232 | } while (!kthread_should_stop() && !fullstop); | 325 | } while (!kthread_should_stop() && !fullstop); | 
| 233 | VERBOSE_PRINTK_STRING("rcu_torture_writer task stopping"); | 326 | VERBOSE_PRINTK_STRING("rcu_torture_writer task stopping"); | 
| 234 | while (!kthread_should_stop()) | 327 | while (!kthread_should_stop()) | 
| @@ -246,6 +339,7 @@ static int | |||
| 246 | rcu_torture_reader(void *arg) | 339 | rcu_torture_reader(void *arg) | 
| 247 | { | 340 | { | 
| 248 | int completed; | 341 | int completed; | 
| 342 | int idx; | ||
| 249 | DEFINE_RCU_RANDOM(rand); | 343 | DEFINE_RCU_RANDOM(rand); | 
| 250 | struct rcu_torture *p; | 344 | struct rcu_torture *p; | 
| 251 | int pipe_count; | 345 | int pipe_count; | 
| @@ -254,12 +348,12 @@ rcu_torture_reader(void *arg) | |||
| 254 | set_user_nice(current, 19); | 348 | set_user_nice(current, 19); | 
| 255 | 349 | ||
| 256 | do { | 350 | do { | 
| 257 | rcu_read_lock(); | 351 | idx = cur_ops->readlock(); | 
| 258 | completed = rcu_batches_completed(); | 352 | completed = cur_ops->completed(); | 
| 259 | p = rcu_dereference(rcu_torture_current); | 353 | p = rcu_dereference(rcu_torture_current); | 
| 260 | if (p == NULL) { | 354 | if (p == NULL) { | 
| 261 | /* Wait for rcu_torture_writer to get underway */ | 355 | /* Wait for rcu_torture_writer to get underway */ | 
| 262 | rcu_read_unlock(); | 356 | cur_ops->readunlock(idx); | 
| 263 | schedule_timeout_interruptible(HZ); | 357 | schedule_timeout_interruptible(HZ); | 
| 264 | continue; | 358 | continue; | 
| 265 | } | 359 | } | 
| @@ -273,14 +367,14 @@ rcu_torture_reader(void *arg) | |||
| 273 | pipe_count = RCU_TORTURE_PIPE_LEN; | 367 | pipe_count = RCU_TORTURE_PIPE_LEN; | 
| 274 | } | 368 | } | 
| 275 | ++__get_cpu_var(rcu_torture_count)[pipe_count]; | 369 | ++__get_cpu_var(rcu_torture_count)[pipe_count]; | 
| 276 | completed = rcu_batches_completed() - completed; | 370 | completed = cur_ops->completed() - completed; | 
| 277 | if (completed > RCU_TORTURE_PIPE_LEN) { | 371 | if (completed > RCU_TORTURE_PIPE_LEN) { | 
| 278 | /* Should not happen, but... */ | 372 | /* Should not happen, but... */ | 
| 279 | completed = RCU_TORTURE_PIPE_LEN; | 373 | completed = RCU_TORTURE_PIPE_LEN; | 
| 280 | } | 374 | } | 
| 281 | ++__get_cpu_var(rcu_torture_batch)[completed]; | 375 | ++__get_cpu_var(rcu_torture_batch)[completed]; | 
| 282 | preempt_enable(); | 376 | preempt_enable(); | 
| 283 | rcu_read_unlock(); | 377 | cur_ops->readunlock(idx); | 
| 284 | schedule(); | 378 | schedule(); | 
| 285 | } while (!kthread_should_stop() && !fullstop); | 379 | } while (!kthread_should_stop() && !fullstop); | 
| 286 | VERBOSE_PRINTK_STRING("rcu_torture_reader task stopping"); | 380 | VERBOSE_PRINTK_STRING("rcu_torture_reader task stopping"); | 
| @@ -311,7 +405,7 @@ rcu_torture_printk(char *page) | |||
| 311 | if (pipesummary[i] != 0) | 405 | if (pipesummary[i] != 0) | 
| 312 | break; | 406 | break; | 
| 313 | } | 407 | } | 
| 314 | cnt += sprintf(&page[cnt], "rcutorture: "); | 408 | cnt += sprintf(&page[cnt], "%s%s ", torture_type, TORTURE_FLAG); | 
| 315 | cnt += sprintf(&page[cnt], | 409 | cnt += sprintf(&page[cnt], | 
| 316 | "rtc: %p ver: %ld tfle: %d rta: %d rtaf: %d rtf: %d " | 410 | "rtc: %p ver: %ld tfle: %d rta: %d rtaf: %d rtf: %d " | 
| 317 | "rtmbe: %d", | 411 | "rtmbe: %d", | 
| @@ -324,7 +418,7 @@ rcu_torture_printk(char *page) | |||
| 324 | atomic_read(&n_rcu_torture_mberror)); | 418 | atomic_read(&n_rcu_torture_mberror)); | 
| 325 | if (atomic_read(&n_rcu_torture_mberror) != 0) | 419 | if (atomic_read(&n_rcu_torture_mberror) != 0) | 
| 326 | cnt += sprintf(&page[cnt], " !!!"); | 420 | cnt += sprintf(&page[cnt], " !!!"); | 
| 327 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 421 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); | 
| 328 | if (i > 1) { | 422 | if (i > 1) { | 
| 329 | cnt += sprintf(&page[cnt], "!!! "); | 423 | cnt += sprintf(&page[cnt], "!!! "); | 
| 330 | atomic_inc(&n_rcu_torture_error); | 424 | atomic_inc(&n_rcu_torture_error); | 
| @@ -332,17 +426,19 @@ rcu_torture_printk(char *page) | |||
| 332 | cnt += sprintf(&page[cnt], "Reader Pipe: "); | 426 | cnt += sprintf(&page[cnt], "Reader Pipe: "); | 
| 333 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) | 427 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) | 
| 334 | cnt += sprintf(&page[cnt], " %ld", pipesummary[i]); | 428 | cnt += sprintf(&page[cnt], " %ld", pipesummary[i]); | 
| 335 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 429 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); | 
| 336 | cnt += sprintf(&page[cnt], "Reader Batch: "); | 430 | cnt += sprintf(&page[cnt], "Reader Batch: "); | 
| 337 | for (i = 0; i < RCU_TORTURE_PIPE_LEN; i++) | 431 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) | 
| 338 | cnt += sprintf(&page[cnt], " %ld", batchsummary[i]); | 432 | cnt += sprintf(&page[cnt], " %ld", batchsummary[i]); | 
| 339 | cnt += sprintf(&page[cnt], "\nrcutorture: "); | 433 | cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG); | 
| 340 | cnt += sprintf(&page[cnt], "Free-Block Circulation: "); | 434 | cnt += sprintf(&page[cnt], "Free-Block Circulation: "); | 
| 341 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { | 435 | for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { | 
| 342 | cnt += sprintf(&page[cnt], " %d", | 436 | cnt += sprintf(&page[cnt], " %d", | 
| 343 | atomic_read(&rcu_torture_wcount[i])); | 437 | atomic_read(&rcu_torture_wcount[i])); | 
| 344 | } | 438 | } | 
| 345 | cnt += sprintf(&page[cnt], "\n"); | 439 | cnt += sprintf(&page[cnt], "\n"); | 
| 440 | if (cur_ops->stats != NULL) | ||
| 441 | cnt += cur_ops->stats(&page[cnt]); | ||
| 346 | return cnt; | 442 | return cnt; | 
| 347 | } | 443 | } | 
| 348 | 444 | ||
| @@ -444,11 +540,11 @@ rcu_torture_shuffle(void *arg) | |||
| 444 | static inline void | 540 | static inline void | 
| 445 | rcu_torture_print_module_parms(char *tag) | 541 | rcu_torture_print_module_parms(char *tag) | 
| 446 | { | 542 | { | 
| 447 | printk(KERN_ALERT TORTURE_FLAG "--- %s: nreaders=%d " | 543 | printk(KERN_ALERT "%s" TORTURE_FLAG "--- %s: nreaders=%d " | 
| 448 | "stat_interval=%d verbose=%d test_no_idle_hz=%d " | 544 | "stat_interval=%d verbose=%d test_no_idle_hz=%d " | 
| 449 | "shuffle_interval = %d\n", | 545 | "shuffle_interval = %d\n", | 
| 450 | tag, nrealreaders, stat_interval, verbose, test_no_idle_hz, | 546 | torture_type, tag, nrealreaders, stat_interval, verbose, | 
| 451 | shuffle_interval); | 547 | test_no_idle_hz, shuffle_interval); | 
| 452 | } | 548 | } | 
| 453 | 549 | ||
| 454 | static void | 550 | static void | 
| @@ -493,6 +589,9 @@ rcu_torture_cleanup(void) | |||
| 493 | rcu_barrier(); | 589 | rcu_barrier(); | 
| 494 | 590 | ||
| 495 | rcu_torture_stats_print(); /* -After- the stats thread is stopped! */ | 591 | rcu_torture_stats_print(); /* -After- the stats thread is stopped! */ | 
| 592 | |||
| 593 | if (cur_ops->cleanup != NULL) | ||
| 594 | cur_ops->cleanup(); | ||
| 496 | if (atomic_read(&n_rcu_torture_error)) | 595 | if (atomic_read(&n_rcu_torture_error)) | 
| 497 | rcu_torture_print_module_parms("End of test: FAILURE"); | 596 | rcu_torture_print_module_parms("End of test: FAILURE"); | 
| 498 | else | 597 | else | 
| @@ -508,6 +607,20 @@ rcu_torture_init(void) | |||
| 508 | 607 | ||
| 509 | /* Process args and tell the world that the torturer is on the job. */ | 608 | /* Process args and tell the world that the torturer is on the job. */ | 
| 510 | 609 | ||
| 610 | for (i = 0; cur_ops = torture_ops[i], cur_ops != NULL; i++) { | ||
| 611 | cur_ops = torture_ops[i]; | ||
| 612 | if (strcmp(torture_type, cur_ops->name) == 0) { | ||
| 613 | break; | ||
| 614 | } | ||
| 615 | } | ||
| 616 | if (cur_ops == NULL) { | ||
| 617 | printk(KERN_ALERT "rcutorture: invalid torture type: \"%s\"\n", | ||
| 618 | torture_type); | ||
| 619 | return (-EINVAL); | ||
| 620 | } | ||
| 621 | if (cur_ops->init != NULL) | ||
| 622 | cur_ops->init(); /* no "goto unwind" prior to this point!!! */ | ||
| 623 | |||
| 511 | if (nreaders >= 0) | 624 | if (nreaders >= 0) | 
| 512 | nrealreaders = nreaders; | 625 | nrealreaders = nreaders; | 
| 513 | else | 626 | else | 
