2005-10-31 07:03:12 +08:00
|
|
|
/*
|
2006-06-27 17:54:02 +08:00
|
|
|
* Read-Copy Update module-based torture test facility
|
2005-10-31 07:03:12 +08:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2013-12-04 02:02:52 +08:00
|
|
|
* along with this program; if not, you can access it online at
|
|
|
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
2005-10-31 07:03:12 +08:00
|
|
|
*
|
2006-10-04 17:17:13 +08:00
|
|
|
* Copyright (C) IBM Corporation, 2005, 2006
|
2005-10-31 07:03:12 +08:00
|
|
|
*
|
|
|
|
* Authors: Paul E. McKenney <paulmck@us.ibm.com>
|
2014-07-31 07:08:42 +08:00
|
|
|
* Josh Triplett <josh@joshtriplett.org>
|
2005-10-31 07:03:12 +08:00
|
|
|
*
|
|
|
|
* See also: Documentation/RCU/torture.txt
|
|
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kthread.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/smp.h>
|
|
|
|
#include <linux/rcupdate.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/sched.h>
|
2011-07-27 07:09:06 +08:00
|
|
|
#include <linux/atomic.h>
|
2005-10-31 07:03:12 +08:00
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/completion.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/percpu.h>
|
|
|
|
#include <linux/notifier.h>
|
2008-12-16 08:13:07 +08:00
|
|
|
#include <linux/reboot.h>
|
2007-07-17 19:03:35 +08:00
|
|
|
#include <linux/freezer.h>
|
2005-10-31 07:03:12 +08:00
|
|
|
#include <linux/cpu.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/stat.h>
|
2006-10-04 17:17:03 +08:00
|
|
|
#include <linux/srcu.h>
|
2008-04-29 15:59:25 +08:00
|
|
|
#include <linux/slab.h>
|
2012-11-15 08:26:40 +08:00
|
|
|
#include <linux/trace_clock.h>
|
2008-10-21 01:23:38 +08:00
|
|
|
#include <asm/byteorder.h>
|
2014-01-28 03:49:39 +08:00
|
|
|
#include <linux/torture.h>
|
2005-10-31 07:03:12 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
2014-07-31 07:08:42 +08:00
|
|
|
MODULE_AUTHOR("Paul E. McKenney <paulmck@us.ibm.com> and Josh Triplett <josh@joshtriplett.org>");
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2013-10-09 11:23:47 +08:00
|
|
|
|
2014-01-28 08:27:00 +08:00
|
|
|
torture_param(int, fqs_duration, 0,
|
|
|
|
"Duration of fqs bursts (us), 0 to disable");
|
|
|
|
torture_param(int, fqs_holdoff, 0, "Holdoff time within fqs bursts (us)");
|
|
|
|
torture_param(int, fqs_stutter, 3, "Wait time between fqs bursts (s)");
|
2014-03-19 06:57:41 +08:00
|
|
|
torture_param(bool, gp_cond, false, "Use conditional/async GP wait primitives");
|
2014-01-28 08:27:00 +08:00
|
|
|
torture_param(bool, gp_exp, false, "Use expedited GP wait primitives");
|
|
|
|
torture_param(bool, gp_normal, false,
|
|
|
|
"Use normal (non-expedited) GP wait primitives");
|
2014-03-22 07:17:56 +08:00
|
|
|
torture_param(bool, gp_sync, false, "Use synchronous GP wait primitives");
|
2014-01-28 08:27:00 +08:00
|
|
|
torture_param(int, irqreader, 1, "Allow RCU readers from irq handlers");
|
|
|
|
torture_param(int, n_barrier_cbs, 0,
|
|
|
|
"# of callbacks/kthreads for barrier testing");
|
|
|
|
torture_param(int, nfakewriters, 4, "Number of RCU fake writer threads");
|
|
|
|
torture_param(int, nreaders, -1, "Number of RCU reader threads");
|
|
|
|
torture_param(int, object_debug, 0,
|
|
|
|
"Enable debug-object double call_rcu() testing");
|
|
|
|
torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");
|
|
|
|
torture_param(int, onoff_interval, 0,
|
|
|
|
"Time between CPU hotplugs (s), 0=disable");
|
|
|
|
torture_param(int, shuffle_interval, 3, "Number of seconds between shuffles");
|
|
|
|
torture_param(int, shutdown_secs, 0, "Shutdown time (s), <= zero to disable.");
|
|
|
|
torture_param(int, stall_cpu, 0, "Stall duration (s), zero to disable.");
|
|
|
|
torture_param(int, stall_cpu_holdoff, 10,
|
|
|
|
"Time to wait before starting stall (s).");
|
|
|
|
torture_param(int, stat_interval, 60,
|
|
|
|
"Number of seconds between stats printk()s");
|
|
|
|
torture_param(int, stutter, 5, "Number of seconds to run/halt test");
|
|
|
|
torture_param(int, test_boost, 1, "Test RCU prio boost: 0=no, 1=maybe, 2=yes.");
|
|
|
|
torture_param(int, test_boost_duration, 4,
|
|
|
|
"Duration of each boost test, seconds.");
|
|
|
|
torture_param(int, test_boost_interval, 7,
|
|
|
|
"Interval between boost tests, seconds.");
|
|
|
|
torture_param(bool, test_no_idle_hz, true,
|
|
|
|
"Test support for tickless idle CPUs");
|
2014-01-31 05:38:09 +08:00
|
|
|
torture_param(bool, verbose, true,
|
|
|
|
"Enable verbose debugging printk()s");
|
2014-01-28 08:27:00 +08:00
|
|
|
|
2013-06-14 06:12:24 +08:00
|
|
|
static char *torture_type = "rcu";
|
2007-03-06 17:42:13 +08:00
|
|
|
module_param(torture_type, charp, 0444);
|
2013-06-14 06:12:24 +08:00
|
|
|
MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu, rcu_bh, ...)");
|
2005-10-31 07:03:12 +08:00
|
|
|
|
|
|
|
static int nrealreaders;
|
|
|
|
static struct task_struct *writer_task;
|
2006-10-04 17:17:13 +08:00
|
|
|
static struct task_struct **fakewriter_tasks;
|
2005-10-31 07:03:12 +08:00
|
|
|
static struct task_struct **reader_tasks;
|
|
|
|
static struct task_struct *stats_task;
|
2010-01-05 07:09:10 +08:00
|
|
|
static struct task_struct *fqs_task;
|
2010-09-03 07:16:14 +08:00
|
|
|
static struct task_struct *boost_tasks[NR_CPUS];
|
2012-01-21 07:36:33 +08:00
|
|
|
static struct task_struct *stall_task;
|
2012-02-21 09:51:45 +08:00
|
|
|
static struct task_struct **barrier_cbs_tasks;
|
|
|
|
static struct task_struct *barrier_task;
|
2005-10-31 07:03:12 +08:00
|
|
|
|
|
|
|
#define RCU_TORTURE_PIPE_LEN 10
|
|
|
|
|
|
|
|
struct rcu_torture {
|
|
|
|
struct rcu_head rtort_rcu;
|
|
|
|
int rtort_pipe_count;
|
|
|
|
struct list_head rtort_free;
|
2005-11-18 17:10:50 +08:00
|
|
|
int rtort_mbtest;
|
2005-10-31 07:03:12 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static LIST_HEAD(rcu_torture_freelist);
|
2010-09-20 12:06:14 +08:00
|
|
|
static struct rcu_torture __rcu *rcu_torture_current;
|
2011-04-04 12:33:51 +08:00
|
|
|
static unsigned long rcu_torture_current_version;
|
2005-10-31 07:03:12 +08:00
|
|
|
static struct rcu_torture rcu_tortures[10 * RCU_TORTURE_PIPE_LEN];
|
|
|
|
static DEFINE_SPINLOCK(rcu_torture_lock);
|
2014-02-18 05:13:05 +08:00
|
|
|
static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1],
|
|
|
|
rcu_torture_count) = { 0 };
|
|
|
|
static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1],
|
|
|
|
rcu_torture_batch) = { 0 };
|
2005-10-31 07:03:12 +08:00
|
|
|
static atomic_t rcu_torture_wcount[RCU_TORTURE_PIPE_LEN + 1];
|
2006-10-04 17:17:03 +08:00
|
|
|
static atomic_t n_rcu_torture_alloc;
|
|
|
|
static atomic_t n_rcu_torture_alloc_fail;
|
|
|
|
static atomic_t n_rcu_torture_free;
|
|
|
|
static atomic_t n_rcu_torture_mberror;
|
|
|
|
static atomic_t n_rcu_torture_error;
|
2012-02-21 09:51:45 +08:00
|
|
|
static long n_rcu_torture_barrier_error;
|
2010-09-03 07:16:14 +08:00
|
|
|
static long n_rcu_torture_boost_ktrerror;
|
|
|
|
static long n_rcu_torture_boost_rterror;
|
|
|
|
static long n_rcu_torture_boost_failure;
|
|
|
|
static long n_rcu_torture_boosts;
|
2009-09-19 01:28:19 +08:00
|
|
|
static long n_rcu_torture_timers;
|
2012-02-21 09:51:45 +08:00
|
|
|
static long n_barrier_attempts;
|
|
|
|
static long n_barrier_successes;
|
2006-10-04 17:17:14 +08:00
|
|
|
static struct list_head rcu_torture_removed;
|
2008-06-18 20:21:44 +08:00
|
|
|
|
2014-02-20 02:51:42 +08:00
|
|
|
static int rcu_torture_writer_state;
|
|
|
|
#define RTWS_FIXED_DELAY 0
|
|
|
|
#define RTWS_DELAY 1
|
|
|
|
#define RTWS_REPLACE 2
|
|
|
|
#define RTWS_DEF_FREE 3
|
|
|
|
#define RTWS_EXP_SYNC 4
|
2014-03-19 06:57:41 +08:00
|
|
|
#define RTWS_COND_GET 5
|
|
|
|
#define RTWS_COND_SYNC 6
|
2014-03-22 07:17:56 +08:00
|
|
|
#define RTWS_SYNC 7
|
|
|
|
#define RTWS_STUTTER 8
|
|
|
|
#define RTWS_STOPPING 9
|
2014-02-20 02:51:42 +08:00
|
|
|
|
2008-06-19 00:26:49 +08:00
|
|
|
#if defined(MODULE) || defined(CONFIG_RCU_TORTURE_TEST_RUNNABLE)
|
|
|
|
#define RCUTORTURE_RUNNABLE_INIT 1
|
|
|
|
#else
|
|
|
|
#define RCUTORTURE_RUNNABLE_INIT 0
|
|
|
|
#endif
|
|
|
|
int rcutorture_runnable = RCUTORTURE_RUNNABLE_INIT;
|
2011-11-05 04:24:07 +08:00
|
|
|
module_param(rcutorture_runnable, int, 0444);
|
|
|
|
MODULE_PARM_DESC(rcutorture_runnable, "Start rcutorture at boot");
|
2008-06-19 00:26:49 +08:00
|
|
|
|
2011-04-18 14:45:23 +08:00
|
|
|
#if defined(CONFIG_RCU_BOOST) && !defined(CONFIG_HOTPLUG_CPU)
|
2010-09-03 07:16:14 +08:00
|
|
|
#define rcu_can_boost() 1
|
2011-04-18 14:45:23 +08:00
|
|
|
#else /* #if defined(CONFIG_RCU_BOOST) && !defined(CONFIG_HOTPLUG_CPU) */
|
2010-09-03 07:16:14 +08:00
|
|
|
#define rcu_can_boost() 0
|
2011-04-18 14:45:23 +08:00
|
|
|
#endif /* #else #if defined(CONFIG_RCU_BOOST) && !defined(CONFIG_HOTPLUG_CPU) */
|
2010-09-03 07:16:14 +08:00
|
|
|
|
2013-02-05 02:36:13 +08:00
|
|
|
#ifdef CONFIG_RCU_TRACE
|
|
|
|
static u64 notrace rcu_trace_clock_local(void)
|
|
|
|
{
|
|
|
|
u64 ts = trace_clock_local();
|
|
|
|
unsigned long __maybe_unused ts_rem = do_div(ts, NSEC_PER_USEC);
|
|
|
|
return ts;
|
|
|
|
}
|
|
|
|
#else /* #ifdef CONFIG_RCU_TRACE */
|
|
|
|
static u64 notrace rcu_trace_clock_local(void)
|
|
|
|
{
|
|
|
|
return 0ULL;
|
|
|
|
}
|
|
|
|
#endif /* #else #ifdef CONFIG_RCU_TRACE */
|
|
|
|
|
2010-09-03 07:16:14 +08:00
|
|
|
static unsigned long boost_starttime; /* jiffies of next boost test start. */
|
2014-06-12 04:39:43 +08:00
|
|
|
static DEFINE_MUTEX(boost_mutex); /* protect setting boost_starttime */
|
2010-09-03 07:16:14 +08:00
|
|
|
/* and boost task create/destroy. */
|
2012-02-21 09:51:45 +08:00
|
|
|
static atomic_t barrier_cbs_count; /* Barrier callbacks registered. */
|
2012-05-29 10:21:41 +08:00
|
|
|
static bool barrier_phase; /* Test phase. */
|
2012-02-21 09:51:45 +08:00
|
|
|
static atomic_t barrier_cbs_invoked; /* Barrier callbacks invoked. */
|
|
|
|
static wait_queue_head_t *barrier_cbs_wq; /* Coordinate barrier testing. */
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(barrier_wq);
|
2010-09-03 07:16:14 +08:00
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
/*
|
|
|
|
* Allocate an element from the rcu_tortures pool.
|
|
|
|
*/
|
2006-01-08 17:02:17 +08:00
|
|
|
static struct rcu_torture *
|
2005-10-31 07:03:12 +08:00
|
|
|
rcu_torture_alloc(void)
|
|
|
|
{
|
|
|
|
struct list_head *p;
|
|
|
|
|
2006-01-26 02:50:12 +08:00
|
|
|
spin_lock_bh(&rcu_torture_lock);
|
2005-10-31 07:03:12 +08:00
|
|
|
if (list_empty(&rcu_torture_freelist)) {
|
|
|
|
atomic_inc(&n_rcu_torture_alloc_fail);
|
2006-01-26 02:50:12 +08:00
|
|
|
spin_unlock_bh(&rcu_torture_lock);
|
2005-10-31 07:03:12 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
atomic_inc(&n_rcu_torture_alloc);
|
|
|
|
p = rcu_torture_freelist.next;
|
|
|
|
list_del_init(p);
|
2006-01-26 02:50:12 +08:00
|
|
|
spin_unlock_bh(&rcu_torture_lock);
|
2005-10-31 07:03:12 +08:00
|
|
|
return container_of(p, struct rcu_torture, rtort_free);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Free an element to the rcu_tortures pool.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
rcu_torture_free(struct rcu_torture *p)
|
|
|
|
{
|
|
|
|
atomic_inc(&n_rcu_torture_free);
|
2006-01-26 02:50:12 +08:00
|
|
|
spin_lock_bh(&rcu_torture_lock);
|
2005-10-31 07:03:12 +08:00
|
|
|
list_add_tail(&p->rtort_free, &rcu_torture_freelist);
|
2006-01-26 02:50:12 +08:00
|
|
|
spin_unlock_bh(&rcu_torture_lock);
|
2005-10-31 07:03:12 +08:00
|
|
|
}
|
|
|
|
|
2006-06-27 17:54:03 +08:00
|
|
|
/*
|
|
|
|
* Operations vector for selecting different types of tests.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct rcu_torture_ops {
|
2014-02-20 02:51:42 +08:00
|
|
|
int ttype;
|
2006-06-27 17:54:03 +08:00
|
|
|
void (*init)(void);
|
|
|
|
int (*readlock)(void);
|
2014-01-28 03:49:39 +08:00
|
|
|
void (*read_delay)(struct torture_random_state *rrsp);
|
2006-06-27 17:54:03 +08:00
|
|
|
void (*readunlock)(int idx);
|
|
|
|
int (*completed)(void);
|
2009-06-26 00:08:17 +08:00
|
|
|
void (*deferred_free)(struct rcu_torture *p);
|
2006-10-04 17:17:13 +08:00
|
|
|
void (*sync)(void);
|
2013-06-13 06:12:21 +08:00
|
|
|
void (*exp_sync)(void);
|
2014-03-19 06:57:41 +08:00
|
|
|
unsigned long (*get_state)(void);
|
|
|
|
void (*cond_sync)(unsigned long oldstate);
|
2012-02-21 09:51:45 +08:00
|
|
|
void (*call)(struct rcu_head *head, void (*func)(struct rcu_head *rcu));
|
2008-05-13 03:21:05 +08:00
|
|
|
void (*cb_barrier)(void);
|
2010-01-05 07:09:10 +08:00
|
|
|
void (*fqs)(void);
|
2014-07-14 21:16:15 +08:00
|
|
|
void (*stats)(void);
|
2009-06-26 00:08:17 +08:00
|
|
|
int irq_capable;
|
2010-09-03 07:16:14 +08:00
|
|
|
int can_boost;
|
2013-07-13 04:50:28 +08:00
|
|
|
const char *name;
|
2006-06-27 17:54:03 +08:00
|
|
|
};
|
2009-09-19 01:28:19 +08:00
|
|
|
|
|
|
|
static struct rcu_torture_ops *cur_ops;
|
2006-06-27 17:54:03 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Definitions for rcu torture testing.
|
|
|
|
*/
|
|
|
|
|
2006-09-29 16:59:30 +08:00
|
|
|
static int rcu_torture_read_lock(void) __acquires(RCU)
|
2006-06-27 17:54:03 +08:00
|
|
|
{
|
|
|
|
rcu_read_lock();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-01-28 03:49:39 +08:00
|
|
|
static void rcu_read_delay(struct torture_random_state *rrsp)
|
2006-10-04 17:17:03 +08:00
|
|
|
{
|
2009-09-09 06:54:35 +08:00
|
|
|
const unsigned long shortdelay_us = 200;
|
|
|
|
const unsigned long longdelay_ms = 50;
|
2006-10-04 17:17:03 +08:00
|
|
|
|
2009-09-09 06:54:35 +08:00
|
|
|
/* We want a short delay sometimes to make a reader delay the grace
|
|
|
|
* period, and we want a long delay occasionally to trigger
|
|
|
|
* force_quiescent_state. */
|
2006-10-04 17:17:03 +08:00
|
|
|
|
2014-01-28 03:49:39 +08:00
|
|
|
if (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms)))
|
2009-09-09 06:54:35 +08:00
|
|
|
mdelay(longdelay_ms);
|
2014-01-28 03:49:39 +08:00
|
|
|
if (!(torture_random(rrsp) % (nrealreaders * 2 * shortdelay_us)))
|
2009-09-09 06:54:35 +08:00
|
|
|
udelay(shortdelay_us);
|
2010-06-21 16:57:42 +08:00
|
|
|
#ifdef CONFIG_PREEMPT
|
2014-01-28 03:49:39 +08:00
|
|
|
if (!preempt_count() &&
|
|
|
|
!(torture_random(rrsp) % (nrealreaders * 20000)))
|
2010-06-21 16:57:42 +08:00
|
|
|
preempt_schedule(); /* No QS if preempt_disable() in effect */
|
|
|
|
#endif
|
2006-10-04 17:17:03 +08:00
|
|
|
}
|
|
|
|
|
2006-09-29 16:59:30 +08:00
|
|
|
static void rcu_torture_read_unlock(int idx) __releases(RCU)
|
2006-06-27 17:54:03 +08:00
|
|
|
{
|
|
|
|
rcu_read_unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rcu_torture_completed(void)
|
|
|
|
{
|
|
|
|
return rcu_batches_completed();
|
|
|
|
}
|
|
|
|
|
2014-03-19 06:57:41 +08:00
|
|
|
/*
|
|
|
|
* Update callback in the pipe. This should be invoked after a grace period.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
rcu_torture_pipe_update_one(struct rcu_torture *rp)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
i = rp->rtort_pipe_count;
|
|
|
|
if (i > RCU_TORTURE_PIPE_LEN)
|
|
|
|
i = RCU_TORTURE_PIPE_LEN;
|
|
|
|
atomic_inc(&rcu_torture_wcount[i]);
|
|
|
|
if (++rp->rtort_pipe_count >= RCU_TORTURE_PIPE_LEN) {
|
|
|
|
rp->rtort_mbtest = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Update all callbacks in the pipe. Suitable for synchronous grace-period
|
|
|
|
* primitives.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
rcu_torture_pipe_update(struct rcu_torture *old_rp)
|
|
|
|
{
|
|
|
|
struct rcu_torture *rp;
|
|
|
|
struct rcu_torture *rp1;
|
|
|
|
|
|
|
|
if (old_rp)
|
|
|
|
list_add(&old_rp->rtort_free, &rcu_torture_removed);
|
|
|
|
list_for_each_entry_safe(rp, rp1, &rcu_torture_removed, rtort_free) {
|
|
|
|
if (rcu_torture_pipe_update_one(rp)) {
|
|
|
|
list_del(&rp->rtort_free);
|
|
|
|
rcu_torture_free(rp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-06-27 17:54:03 +08:00
|
|
|
static void
|
|
|
|
rcu_torture_cb(struct rcu_head *p)
|
|
|
|
{
|
|
|
|
struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu);
|
|
|
|
|
2014-01-31 07:49:29 +08:00
|
|
|
if (torture_must_stop_irq()) {
|
2006-06-27 17:54:03 +08:00
|
|
|
/* Test is ending, just drop callbacks on the floor. */
|
|
|
|
/* The next initialization will pick up the pieces. */
|
|
|
|
return;
|
|
|
|
}
|
2014-03-19 06:57:41 +08:00
|
|
|
if (rcu_torture_pipe_update_one(rp))
|
2006-06-27 17:54:03 +08:00
|
|
|
rcu_torture_free(rp);
|
2014-03-19 06:57:41 +08:00
|
|
|
else
|
2009-06-26 00:08:17 +08:00
|
|
|
cur_ops->deferred_free(rp);
|
2006-06-27 17:54:03 +08:00
|
|
|
}
|
|
|
|
|
2009-12-03 04:10:15 +08:00
|
|
|
static int rcu_no_completed(void)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-06-27 17:54:03 +08:00
|
|
|
static void rcu_torture_deferred_free(struct rcu_torture *p)
|
|
|
|
{
|
|
|
|
call_rcu(&p->rtort_rcu, rcu_torture_cb);
|
|
|
|
}
|
|
|
|
|
2006-10-04 17:17:14 +08:00
|
|
|
static void rcu_sync_torture_init(void)
|
|
|
|
{
|
|
|
|
INIT_LIST_HEAD(&rcu_torture_removed);
|
|
|
|
}
|
|
|
|
|
2013-06-13 06:12:21 +08:00
|
|
|
static struct rcu_torture_ops rcu_ops = {
|
2014-02-20 02:51:42 +08:00
|
|
|
.ttype = RCU_FLAVOR,
|
2009-06-26 00:08:17 +08:00
|
|
|
.init = rcu_sync_torture_init,
|
|
|
|
.readlock = rcu_torture_read_lock,
|
|
|
|
.read_delay = rcu_read_delay,
|
|
|
|
.readunlock = rcu_torture_read_unlock,
|
|
|
|
.completed = rcu_torture_completed,
|
2013-06-13 06:12:21 +08:00
|
|
|
.deferred_free = rcu_torture_deferred_free,
|
2009-06-26 00:08:17 +08:00
|
|
|
.sync = synchronize_rcu,
|
2013-06-13 06:12:21 +08:00
|
|
|
.exp_sync = synchronize_rcu_expedited,
|
2014-03-19 06:57:41 +08:00
|
|
|
.get_state = get_state_synchronize_rcu,
|
|
|
|
.cond_sync = cond_synchronize_rcu,
|
2013-06-13 06:12:21 +08:00
|
|
|
.call = call_rcu,
|
|
|
|
.cb_barrier = rcu_barrier,
|
2010-01-05 07:09:10 +08:00
|
|
|
.fqs = rcu_force_quiescent_state,
|
2009-12-03 04:10:15 +08:00
|
|
|
.stats = NULL,
|
|
|
|
.irq_capable = 1,
|
2010-09-03 07:16:14 +08:00
|
|
|
.can_boost = rcu_can_boost(),
|
2013-06-13 06:12:21 +08:00
|
|
|
.name = "rcu"
|
2009-12-03 04:10:15 +08:00
|
|
|
};
|
|
|
|
|
2006-06-27 17:54:04 +08:00
|
|
|
/*
|
|
|
|
* Definitions for rcu_bh torture testing.
|
|
|
|
*/
|
|
|
|
|
2006-09-29 16:59:30 +08:00
|
|
|
static int rcu_bh_torture_read_lock(void) __acquires(RCU_BH)
|
2006-06-27 17:54:04 +08:00
|
|
|
{
|
|
|
|
rcu_read_lock_bh();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-09-29 16:59:30 +08:00
|
|
|
static void rcu_bh_torture_read_unlock(int idx) __releases(RCU_BH)
|
2006-06-27 17:54:04 +08:00
|
|
|
{
|
|
|
|
rcu_read_unlock_bh();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rcu_bh_torture_completed(void)
|
|
|
|
{
|
|
|
|
return rcu_batches_completed_bh();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rcu_bh_torture_deferred_free(struct rcu_torture *p)
|
|
|
|
{
|
|
|
|
call_rcu_bh(&p->rtort_rcu, rcu_torture_cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct rcu_torture_ops rcu_bh_ops = {
|
2014-02-20 02:51:42 +08:00
|
|
|
.ttype = RCU_BH_FLAVOR,
|
2013-06-13 06:12:21 +08:00
|
|
|
.init = rcu_sync_torture_init,
|
2009-06-26 00:08:17 +08:00
|
|
|
.readlock = rcu_bh_torture_read_lock,
|
|
|
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */
|
|
|
|
.readunlock = rcu_bh_torture_read_unlock,
|
|
|
|
.completed = rcu_bh_torture_completed,
|
|
|
|
.deferred_free = rcu_bh_torture_deferred_free,
|
2011-06-08 07:59:35 +08:00
|
|
|
.sync = synchronize_rcu_bh,
|
2013-06-13 06:12:21 +08:00
|
|
|
.exp_sync = synchronize_rcu_bh_expedited,
|
2012-02-21 09:51:45 +08:00
|
|
|
.call = call_rcu_bh,
|
2009-06-26 00:08:17 +08:00
|
|
|
.cb_barrier = rcu_barrier_bh,
|
2010-01-05 07:09:10 +08:00
|
|
|
.fqs = rcu_bh_force_quiescent_state,
|
2009-06-26 00:08:17 +08:00
|
|
|
.stats = NULL,
|
|
|
|
.irq_capable = 1,
|
|
|
|
.name = "rcu_bh"
|
2006-06-27 17:54:04 +08:00
|
|
|
};
|
|
|
|
|
2014-02-07 00:45:56 +08:00
|
|
|
/*
|
|
|
|
* Don't even think about trying any of these in real life!!!
|
|
|
|
* The names includes "busted", and they really means it!
|
|
|
|
* The only purpose of these functions is to provide a buggy RCU
|
|
|
|
* implementation to make sure that rcutorture correctly emits
|
|
|
|
* buggy-RCU error messages.
|
|
|
|
*/
|
|
|
|
static void rcu_busted_torture_deferred_free(struct rcu_torture *p)
|
|
|
|
{
|
|
|
|
/* This is a deliberate bug for testing purposes only! */
|
|
|
|
rcu_torture_cb(&p->rtort_rcu);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void synchronize_rcu_busted(void)
|
|
|
|
{
|
|
|
|
/* This is a deliberate bug for testing purposes only! */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
call_rcu_busted(struct rcu_head *head, void (*func)(struct rcu_head *rcu))
|
|
|
|
{
|
|
|
|
/* This is a deliberate bug for testing purposes only! */
|
|
|
|
func(head);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct rcu_torture_ops rcu_busted_ops = {
|
2014-02-20 02:51:42 +08:00
|
|
|
.ttype = INVALID_RCU_FLAVOR,
|
2014-02-07 00:45:56 +08:00
|
|
|
.init = rcu_sync_torture_init,
|
|
|
|
.readlock = rcu_torture_read_lock,
|
|
|
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */
|
|
|
|
.readunlock = rcu_torture_read_unlock,
|
|
|
|
.completed = rcu_no_completed,
|
|
|
|
.deferred_free = rcu_busted_torture_deferred_free,
|
|
|
|
.sync = synchronize_rcu_busted,
|
|
|
|
.exp_sync = synchronize_rcu_busted,
|
|
|
|
.call = call_rcu_busted,
|
|
|
|
.cb_barrier = NULL,
|
|
|
|
.fqs = NULL,
|
|
|
|
.stats = NULL,
|
|
|
|
.irq_capable = 1,
|
|
|
|
.name = "rcu_busted"
|
|
|
|
};
|
|
|
|
|
2006-10-04 17:17:03 +08:00
|
|
|
/*
|
|
|
|
* Definitions for srcu torture testing.
|
|
|
|
*/
|
|
|
|
|
2012-10-13 01:14:17 +08:00
|
|
|
DEFINE_STATIC_SRCU(srcu_ctl);
|
2006-10-04 17:17:03 +08:00
|
|
|
|
2006-12-07 12:40:19 +08:00
|
|
|
static int srcu_torture_read_lock(void) __acquires(&srcu_ctl)
|
2006-10-04 17:17:03 +08:00
|
|
|
{
|
|
|
|
return srcu_read_lock(&srcu_ctl);
|
|
|
|
}
|
|
|
|
|
2014-01-28 03:49:39 +08:00
|
|
|
static void srcu_read_delay(struct torture_random_state *rrsp)
|
2006-10-04 17:17:03 +08:00
|
|
|
{
|
|
|
|
long delay;
|
|
|
|
const long uspertick = 1000000 / HZ;
|
|
|
|
const long longdelay = 10;
|
|
|
|
|
|
|
|
/* We want there to be long-running readers, but not all the time. */
|
|
|
|
|
2014-01-28 03:49:39 +08:00
|
|
|
delay = torture_random(rrsp) %
|
|
|
|
(nrealreaders * 2 * longdelay * uspertick);
|
2006-10-04 17:17:03 +08:00
|
|
|
if (!delay)
|
|
|
|
schedule_timeout_interruptible(longdelay);
|
2010-06-21 16:57:42 +08:00
|
|
|
else
|
|
|
|
rcu_read_delay(rrsp);
|
2006-10-04 17:17:03 +08:00
|
|
|
}
|
|
|
|
|
2006-12-07 12:40:19 +08:00
|
|
|
static void srcu_torture_read_unlock(int idx) __releases(&srcu_ctl)
|
2006-10-04 17:17:03 +08:00
|
|
|
{
|
|
|
|
srcu_read_unlock(&srcu_ctl, idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int srcu_torture_completed(void)
|
|
|
|
{
|
|
|
|
return srcu_batches_completed(&srcu_ctl);
|
|
|
|
}
|
|
|
|
|
2012-03-19 16:12:14 +08:00
|
|
|
static void srcu_torture_deferred_free(struct rcu_torture *rp)
|
|
|
|
{
|
|
|
|
call_srcu(&srcu_ctl, &rp->rtort_rcu, rcu_torture_cb);
|
|
|
|
}
|
|
|
|
|
2006-10-04 17:17:13 +08:00
|
|
|
static void srcu_torture_synchronize(void)
|
|
|
|
{
|
|
|
|
synchronize_srcu(&srcu_ctl);
|
|
|
|
}
|
|
|
|
|
2012-05-09 01:21:50 +08:00
|
|
|
static void srcu_torture_call(struct rcu_head *head,
|
|
|
|
void (*func)(struct rcu_head *head))
|
|
|
|
{
|
|
|
|
call_srcu(&srcu_ctl, head, func);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void srcu_torture_barrier(void)
|
|
|
|
{
|
|
|
|
srcu_barrier(&srcu_ctl);
|
|
|
|
}
|
|
|
|
|
2014-07-14 21:16:15 +08:00
|
|
|
static void srcu_torture_stats(void)
|
2006-10-04 17:17:03 +08:00
|
|
|
{
|
|
|
|
int cpu;
|
|
|
|
int idx = srcu_ctl.completed & 0x1;
|
|
|
|
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_alert("%s%s per-CPU(idx=%d):",
|
|
|
|
torture_type, TORTURE_FLAG, idx);
|
2006-10-04 17:17:03 +08:00
|
|
|
for_each_possible_cpu(cpu) {
|
2014-03-04 08:51:08 +08:00
|
|
|
long c0, c1;
|
|
|
|
|
|
|
|
c0 = (long)per_cpu_ptr(srcu_ctl.per_cpu_ref, cpu)->c[!idx];
|
|
|
|
c1 = (long)per_cpu_ptr(srcu_ctl.per_cpu_ref, cpu)->c[idx];
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont(" %d(%ld,%ld)", cpu, c0, c1);
|
2006-10-04 17:17:03 +08:00
|
|
|
}
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont("\n");
|
2006-10-04 17:17:03 +08:00
|
|
|
}
|
|
|
|
|
2013-06-13 06:12:21 +08:00
|
|
|
static void srcu_torture_synchronize_expedited(void)
|
|
|
|
{
|
|
|
|
synchronize_srcu_expedited(&srcu_ctl);
|
|
|
|
}
|
|
|
|
|
2006-10-04 17:17:03 +08:00
|
|
|
static struct rcu_torture_ops srcu_ops = {
|
2014-02-20 02:51:42 +08:00
|
|
|
.ttype = SRCU_FLAVOR,
|
2012-10-13 01:14:17 +08:00
|
|
|
.init = rcu_sync_torture_init,
|
2009-06-26 00:08:17 +08:00
|
|
|
.readlock = srcu_torture_read_lock,
|
|
|
|
.read_delay = srcu_read_delay,
|
|
|
|
.readunlock = srcu_torture_read_unlock,
|
|
|
|
.completed = srcu_torture_completed,
|
2012-03-19 16:12:14 +08:00
|
|
|
.deferred_free = srcu_torture_deferred_free,
|
2009-06-26 00:08:17 +08:00
|
|
|
.sync = srcu_torture_synchronize,
|
2013-06-13 06:12:21 +08:00
|
|
|
.exp_sync = srcu_torture_synchronize_expedited,
|
2012-05-09 01:21:50 +08:00
|
|
|
.call = srcu_torture_call,
|
|
|
|
.cb_barrier = srcu_torture_barrier,
|
2009-06-26 00:08:17 +08:00
|
|
|
.stats = srcu_torture_stats,
|
|
|
|
.name = "srcu"
|
2006-10-04 17:17:03 +08:00
|
|
|
};
|
|
|
|
|
2006-10-04 17:17:16 +08:00
|
|
|
/*
|
|
|
|
* Definitions for sched torture testing.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int sched_torture_read_lock(void)
|
|
|
|
{
|
|
|
|
preempt_disable();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sched_torture_read_unlock(int idx)
|
|
|
|
{
|
|
|
|
preempt_enable();
|
|
|
|
}
|
|
|
|
|
2008-05-13 03:21:05 +08:00
|
|
|
static void rcu_sched_torture_deferred_free(struct rcu_torture *p)
|
|
|
|
{
|
|
|
|
call_rcu_sched(&p->rtort_rcu, rcu_torture_cb);
|
|
|
|
}
|
|
|
|
|
2006-10-04 17:17:16 +08:00
|
|
|
static struct rcu_torture_ops sched_ops = {
|
2014-02-20 02:51:42 +08:00
|
|
|
.ttype = RCU_SCHED_FLAVOR,
|
2009-06-26 00:08:17 +08:00
|
|
|
.init = rcu_sync_torture_init,
|
|
|
|
.readlock = sched_torture_read_lock,
|
|
|
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */
|
|
|
|
.readunlock = sched_torture_read_unlock,
|
2009-12-03 04:10:15 +08:00
|
|
|
.completed = rcu_no_completed,
|
2009-06-26 00:08:17 +08:00
|
|
|
.deferred_free = rcu_sched_torture_deferred_free,
|
2011-06-08 07:59:35 +08:00
|
|
|
.sync = synchronize_sched,
|
2013-06-13 06:12:21 +08:00
|
|
|
.exp_sync = synchronize_sched_expedited,
|
|
|
|
.call = call_rcu_sched,
|
2009-06-26 00:08:17 +08:00
|
|
|
.cb_barrier = rcu_barrier_sched,
|
2010-01-05 07:09:10 +08:00
|
|
|
.fqs = rcu_sched_force_quiescent_state,
|
2009-06-26 00:08:17 +08:00
|
|
|
.stats = NULL,
|
|
|
|
.irq_capable = 1,
|
|
|
|
.name = "sched"
|
2006-10-04 17:17:16 +08:00
|
|
|
};
|
|
|
|
|
2010-09-03 07:16:14 +08:00
|
|
|
/*
|
|
|
|
* RCU torture priority-boost testing. Runs one real-time thread per
|
|
|
|
* CPU for moderate bursts, repeatedly registering RCU callbacks and
|
|
|
|
* spinning waiting for them to be invoked. If a given callback takes
|
|
|
|
* too long to be invoked, we assume that priority inversion has occurred.
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct rcu_boost_inflight {
|
|
|
|
struct rcu_head rcu;
|
|
|
|
int inflight;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void rcu_torture_boost_cb(struct rcu_head *head)
|
|
|
|
{
|
|
|
|
struct rcu_boost_inflight *rbip =
|
|
|
|
container_of(head, struct rcu_boost_inflight, rcu);
|
|
|
|
|
|
|
|
smp_mb(); /* Ensure RCU-core accesses precede clearing ->inflight */
|
|
|
|
rbip->inflight = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rcu_torture_boost(void *arg)
|
|
|
|
{
|
|
|
|
unsigned long call_rcu_time;
|
|
|
|
unsigned long endtime;
|
|
|
|
unsigned long oldstarttime;
|
|
|
|
struct rcu_boost_inflight rbi = { .inflight = 0 };
|
|
|
|
struct sched_param sp;
|
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_boost started");
|
2010-09-03 07:16:14 +08:00
|
|
|
|
|
|
|
/* Set real-time priority. */
|
|
|
|
sp.sched_priority = 1;
|
|
|
|
if (sched_setscheduler(current, SCHED_FIFO, &sp) < 0) {
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_boost RT prio failed!");
|
2010-09-03 07:16:14 +08:00
|
|
|
n_rcu_torture_boost_rterror++;
|
|
|
|
}
|
|
|
|
|
2011-03-31 00:10:44 +08:00
|
|
|
init_rcu_head_on_stack(&rbi.rcu);
|
2010-09-03 07:16:14 +08:00
|
|
|
/* Each pass through the following loop does one boost-test cycle. */
|
|
|
|
do {
|
|
|
|
/* Wait for the next test interval. */
|
|
|
|
oldstarttime = boost_starttime;
|
2011-08-18 03:39:34 +08:00
|
|
|
while (ULONG_CMP_LT(jiffies, oldstarttime)) {
|
2013-01-11 08:21:07 +08:00
|
|
|
schedule_timeout_interruptible(oldstarttime - jiffies);
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_boost");
|
2014-01-31 07:49:29 +08:00
|
|
|
if (torture_must_stop())
|
2010-09-03 07:16:14 +08:00
|
|
|
goto checkwait;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Do one boost-test interval. */
|
|
|
|
endtime = oldstarttime + test_boost_duration * HZ;
|
|
|
|
call_rcu_time = jiffies;
|
2011-08-18 03:39:34 +08:00
|
|
|
while (ULONG_CMP_LT(jiffies, endtime)) {
|
2010-09-03 07:16:14 +08:00
|
|
|
/* If we don't have a callback in flight, post one. */
|
|
|
|
if (!rbi.inflight) {
|
|
|
|
smp_mb(); /* RCU core before ->inflight = 1. */
|
|
|
|
rbi.inflight = 1;
|
|
|
|
call_rcu(&rbi.rcu, rcu_torture_boost_cb);
|
|
|
|
if (jiffies - call_rcu_time >
|
|
|
|
test_boost_duration * HZ - HZ / 2) {
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_boost boosting failed");
|
2010-09-03 07:16:14 +08:00
|
|
|
n_rcu_torture_boost_failure++;
|
|
|
|
}
|
|
|
|
call_rcu_time = jiffies;
|
|
|
|
}
|
|
|
|
cond_resched();
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_boost");
|
2014-01-31 07:49:29 +08:00
|
|
|
if (torture_must_stop())
|
2010-09-03 07:16:14 +08:00
|
|
|
goto checkwait;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the start time of the next test interval.
|
|
|
|
* Yes, this is vulnerable to long delays, but such
|
|
|
|
* delays simply cause a false negative for the next
|
|
|
|
* interval. Besides, we are running at RT priority,
|
|
|
|
* so delays should be relatively rare.
|
|
|
|
*/
|
2011-08-19 00:30:32 +08:00
|
|
|
while (oldstarttime == boost_starttime &&
|
|
|
|
!kthread_should_stop()) {
|
2010-09-03 07:16:14 +08:00
|
|
|
if (mutex_trylock(&boost_mutex)) {
|
|
|
|
boost_starttime = jiffies +
|
|
|
|
test_boost_interval * HZ;
|
|
|
|
n_rcu_torture_boosts++;
|
|
|
|
mutex_unlock(&boost_mutex);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
schedule_timeout_uninterruptible(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Go do the stutter. */
|
2014-02-01 03:57:43 +08:00
|
|
|
checkwait: stutter_wait("rcu_torture_boost");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2010-09-03 07:16:14 +08:00
|
|
|
|
|
|
|
/* Clean up and exit. */
|
2014-02-01 09:37:28 +08:00
|
|
|
while (!kthread_should_stop() || rbi.inflight) {
|
|
|
|
torture_shutdown_absorb("rcu_torture_boost");
|
2010-09-03 07:16:14 +08:00
|
|
|
schedule_timeout_uninterruptible(1);
|
2014-02-01 09:37:28 +08:00
|
|
|
}
|
2010-09-03 07:16:14 +08:00
|
|
|
smp_mb(); /* order accesses to ->inflight before stack-frame death. */
|
2011-06-21 16:48:03 +08:00
|
|
|
destroy_rcu_head_on_stack(&rbi.rcu);
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_boost");
|
2010-09-03 07:16:14 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-05 07:09:10 +08:00
|
|
|
/*
|
|
|
|
* RCU torture force-quiescent-state kthread. Repeatedly induces
|
|
|
|
* bursts of calls to force_quiescent_state(), increasing the probability
|
|
|
|
* of occurrence of some important types of race conditions.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
rcu_torture_fqs(void *arg)
|
|
|
|
{
|
|
|
|
unsigned long fqs_resume_time;
|
|
|
|
int fqs_burst_remaining;
|
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_fqs task started");
|
2010-01-05 07:09:10 +08:00
|
|
|
do {
|
|
|
|
fqs_resume_time = jiffies + fqs_stutter * HZ;
|
2011-08-18 03:39:34 +08:00
|
|
|
while (ULONG_CMP_LT(jiffies, fqs_resume_time) &&
|
|
|
|
!kthread_should_stop()) {
|
2010-01-05 07:09:10 +08:00
|
|
|
schedule_timeout_interruptible(1);
|
|
|
|
}
|
|
|
|
fqs_burst_remaining = fqs_duration;
|
2011-08-18 03:39:34 +08:00
|
|
|
while (fqs_burst_remaining > 0 &&
|
|
|
|
!kthread_should_stop()) {
|
2010-01-05 07:09:10 +08:00
|
|
|
cur_ops->fqs();
|
|
|
|
udelay(fqs_holdoff);
|
|
|
|
fqs_burst_remaining -= fqs_holdoff;
|
|
|
|
}
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_fqs");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_fqs");
|
2010-01-05 07:09:10 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
/*
|
|
|
|
* RCU torture writer kthread. Repeatedly substitutes a new structure
|
|
|
|
* for that pointed to by rcu_torture_current, freeing the old structure
|
|
|
|
* after a series of grace periods (the "pipeline").
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
rcu_torture_writer(void *arg)
|
|
|
|
{
|
2014-03-19 06:57:41 +08:00
|
|
|
unsigned long gp_snap;
|
|
|
|
bool gp_cond1 = gp_cond, gp_exp1 = gp_exp, gp_normal1 = gp_normal;
|
2014-03-22 07:17:56 +08:00
|
|
|
bool gp_sync1 = gp_sync;
|
2005-10-31 07:03:12 +08:00
|
|
|
int i;
|
|
|
|
struct rcu_torture *rp;
|
|
|
|
struct rcu_torture *old_rp;
|
2014-01-28 03:49:39 +08:00
|
|
|
static DEFINE_TORTURE_RANDOM(rand);
|
2014-03-22 07:17:56 +08:00
|
|
|
int synctype[] = { RTWS_DEF_FREE, RTWS_EXP_SYNC,
|
|
|
|
RTWS_COND_GET, RTWS_SYNC };
|
2014-03-19 06:57:41 +08:00
|
|
|
int nsynctypes = 0;
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_writer task started");
|
2005-11-14 08:07:22 +08:00
|
|
|
|
2014-03-19 06:57:41 +08:00
|
|
|
/* Initialize synctype[] array. If none set, take default. */
|
2014-03-22 07:17:56 +08:00
|
|
|
if (!gp_cond1 && !gp_exp1 && !gp_normal1 && !gp_sync)
|
|
|
|
gp_cond1 = gp_exp1 = gp_normal1 = gp_sync1 = true;
|
2014-03-19 06:57:41 +08:00
|
|
|
if (gp_cond1 && cur_ops->get_state && cur_ops->cond_sync)
|
|
|
|
synctype[nsynctypes++] = RTWS_COND_GET;
|
|
|
|
else if (gp_cond && (!cur_ops->get_state || !cur_ops->cond_sync))
|
|
|
|
pr_alert("rcu_torture_writer: gp_cond without primitives.\n");
|
|
|
|
if (gp_exp1 && cur_ops->exp_sync)
|
|
|
|
synctype[nsynctypes++] = RTWS_EXP_SYNC;
|
|
|
|
else if (gp_exp && !cur_ops->exp_sync)
|
|
|
|
pr_alert("rcu_torture_writer: gp_exp without primitives.\n");
|
|
|
|
if (gp_normal1 && cur_ops->deferred_free)
|
|
|
|
synctype[nsynctypes++] = RTWS_DEF_FREE;
|
|
|
|
else if (gp_normal && !cur_ops->deferred_free)
|
|
|
|
pr_alert("rcu_torture_writer: gp_normal without primitives.\n");
|
2014-03-22 07:17:56 +08:00
|
|
|
if (gp_sync1 && cur_ops->sync)
|
|
|
|
synctype[nsynctypes++] = RTWS_SYNC;
|
|
|
|
else if (gp_sync && !cur_ops->sync)
|
|
|
|
pr_alert("rcu_torture_writer: gp_sync without primitives.\n");
|
2014-03-19 06:57:41 +08:00
|
|
|
if (WARN_ONCE(nsynctypes == 0,
|
|
|
|
"rcu_torture_writer: No update-side primitives.\n")) {
|
2014-03-22 07:17:56 +08:00
|
|
|
/*
|
|
|
|
* No updates primitives, so don't try updating.
|
|
|
|
* The resulting test won't be testing much, hence the
|
|
|
|
* above WARN_ONCE().
|
|
|
|
*/
|
2014-03-19 06:57:41 +08:00
|
|
|
rcu_torture_writer_state = RTWS_STOPPING;
|
|
|
|
torture_kthread_stopping("rcu_torture_writer");
|
|
|
|
}
|
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
do {
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_FIXED_DELAY;
|
2005-10-31 07:03:12 +08:00
|
|
|
schedule_timeout_uninterruptible(1);
|
2009-09-19 01:28:19 +08:00
|
|
|
rp = rcu_torture_alloc();
|
|
|
|
if (rp == NULL)
|
2005-10-31 07:03:12 +08:00
|
|
|
continue;
|
|
|
|
rp->rtort_pipe_count = 0;
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_DELAY;
|
2014-01-28 03:49:39 +08:00
|
|
|
udelay(torture_random(&rand) & 0x3ff);
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_REPLACE;
|
2010-09-20 12:06:14 +08:00
|
|
|
old_rp = rcu_dereference_check(rcu_torture_current,
|
|
|
|
current == writer_task);
|
2005-11-18 17:10:50 +08:00
|
|
|
rp->rtort_mbtest = 1;
|
2005-10-31 07:03:12 +08:00
|
|
|
rcu_assign_pointer(rcu_torture_current, rp);
|
2009-09-24 00:50:43 +08:00
|
|
|
smp_wmb(); /* Mods to old_rp must follow rcu_assign_pointer() */
|
2007-05-08 15:33:20 +08:00
|
|
|
if (old_rp) {
|
2005-10-31 07:03:12 +08:00
|
|
|
i = old_rp->rtort_pipe_count;
|
|
|
|
if (i > RCU_TORTURE_PIPE_LEN)
|
|
|
|
i = RCU_TORTURE_PIPE_LEN;
|
|
|
|
atomic_inc(&rcu_torture_wcount[i]);
|
|
|
|
old_rp->rtort_pipe_count++;
|
2014-03-19 06:57:41 +08:00
|
|
|
switch (synctype[torture_random(&rand) % nsynctypes]) {
|
|
|
|
case RTWS_DEF_FREE:
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_DEF_FREE;
|
2013-06-13 06:12:21 +08:00
|
|
|
cur_ops->deferred_free(old_rp);
|
2014-03-19 06:57:41 +08:00
|
|
|
break;
|
|
|
|
case RTWS_EXP_SYNC:
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_EXP_SYNC;
|
2013-06-13 06:12:21 +08:00
|
|
|
cur_ops->exp_sync();
|
2014-03-19 06:57:41 +08:00
|
|
|
rcu_torture_pipe_update(old_rp);
|
|
|
|
break;
|
|
|
|
case RTWS_COND_GET:
|
|
|
|
rcu_torture_writer_state = RTWS_COND_GET;
|
|
|
|
gp_snap = cur_ops->get_state();
|
|
|
|
i = torture_random(&rand) % 16;
|
|
|
|
if (i != 0)
|
|
|
|
schedule_timeout_interruptible(i);
|
|
|
|
udelay(torture_random(&rand) % 1000);
|
|
|
|
rcu_torture_writer_state = RTWS_COND_SYNC;
|
|
|
|
cur_ops->cond_sync(gp_snap);
|
|
|
|
rcu_torture_pipe_update(old_rp);
|
|
|
|
break;
|
2014-03-22 07:17:56 +08:00
|
|
|
case RTWS_SYNC:
|
|
|
|
rcu_torture_writer_state = RTWS_SYNC;
|
|
|
|
cur_ops->sync();
|
|
|
|
rcu_torture_pipe_update(old_rp);
|
|
|
|
break;
|
2014-03-19 06:57:41 +08:00
|
|
|
default:
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
break;
|
2013-06-13 06:12:21 +08:00
|
|
|
}
|
2005-10-31 07:03:12 +08:00
|
|
|
}
|
2011-04-04 12:33:51 +08:00
|
|
|
rcutorture_record_progress(++rcu_torture_current_version);
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_STUTTER;
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_writer");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2014-02-20 02:51:42 +08:00
|
|
|
rcu_torture_writer_state = RTWS_STOPPING;
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_writer");
|
2005-10-31 07:03:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-10-04 17:17:13 +08:00
|
|
|
/*
|
|
|
|
* RCU torture fake writer kthread. Repeatedly calls sync, with a random
|
|
|
|
* delay between calls.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
rcu_torture_fakewriter(void *arg)
|
|
|
|
{
|
2014-01-28 03:49:39 +08:00
|
|
|
DEFINE_TORTURE_RANDOM(rand);
|
2006-10-04 17:17:13 +08:00
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_fakewriter task started");
|
2014-02-11 15:34:49 +08:00
|
|
|
set_user_nice(current, MAX_NICE);
|
2006-10-04 17:17:13 +08:00
|
|
|
|
|
|
|
do {
|
2014-01-28 03:49:39 +08:00
|
|
|
schedule_timeout_uninterruptible(1 + torture_random(&rand)%10);
|
|
|
|
udelay(torture_random(&rand) & 0x3ff);
|
2012-05-30 08:50:51 +08:00
|
|
|
if (cur_ops->cb_barrier != NULL &&
|
2014-01-28 03:49:39 +08:00
|
|
|
torture_random(&rand) % (nfakewriters * 8) == 0) {
|
2012-05-30 08:50:51 +08:00
|
|
|
cur_ops->cb_barrier();
|
2013-06-13 06:12:21 +08:00
|
|
|
} else if (gp_normal == gp_exp) {
|
2014-01-28 03:49:39 +08:00
|
|
|
if (torture_random(&rand) & 0x80)
|
2013-06-13 06:12:21 +08:00
|
|
|
cur_ops->sync();
|
|
|
|
else
|
|
|
|
cur_ops->exp_sync();
|
|
|
|
} else if (gp_normal) {
|
2012-05-30 08:50:51 +08:00
|
|
|
cur_ops->sync();
|
2013-06-13 06:12:21 +08:00
|
|
|
} else {
|
|
|
|
cur_ops->exp_sync();
|
|
|
|
}
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_fakewriter");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2006-10-04 17:17:13 +08:00
|
|
|
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_fakewriter");
|
2006-10-04 17:17:13 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-02-27 19:46:57 +08:00
|
|
|
static void rcutorture_trace_dump(void)
|
2011-10-02 22:44:32 +08:00
|
|
|
{
|
|
|
|
static atomic_t beenhere = ATOMIC_INIT(0);
|
|
|
|
|
|
|
|
if (atomic_read(&beenhere))
|
|
|
|
return;
|
|
|
|
if (atomic_xchg(&beenhere, 1) != 0)
|
|
|
|
return;
|
|
|
|
ftrace_dump(DUMP_ALL);
|
|
|
|
}
|
|
|
|
|
2008-06-26 03:24:52 +08:00
|
|
|
/*
|
|
|
|
* RCU torture reader from timer handler. Dereferences rcu_torture_current,
|
|
|
|
* incrementing the corresponding element of the pipeline array. The
|
|
|
|
* counter in the element should never be greater than 1, otherwise, the
|
|
|
|
* RCU implementation is broken.
|
|
|
|
*/
|
|
|
|
static void rcu_torture_timer(unsigned long unused)
|
|
|
|
{
|
|
|
|
int idx;
|
|
|
|
int completed;
|
2012-11-15 08:26:40 +08:00
|
|
|
int completed_end;
|
2014-01-28 03:49:39 +08:00
|
|
|
static DEFINE_TORTURE_RANDOM(rand);
|
2008-06-26 03:24:52 +08:00
|
|
|
static DEFINE_SPINLOCK(rand_lock);
|
|
|
|
struct rcu_torture *p;
|
|
|
|
int pipe_count;
|
2012-11-15 08:26:40 +08:00
|
|
|
unsigned long long ts;
|
2008-06-26 03:24:52 +08:00
|
|
|
|
|
|
|
idx = cur_ops->readlock();
|
|
|
|
completed = cur_ops->completed();
|
2013-02-05 02:36:13 +08:00
|
|
|
ts = rcu_trace_clock_local();
|
rcu: Introduce lockdep-based checking to RCU read-side primitives
Inspection is proving insufficient to catch all RCU misuses,
which is understandable given that rcu_dereference() might be
protected by any of four different flavors of RCU (RCU, RCU-bh,
RCU-sched, and SRCU), and might also/instead be protected by any
of a number of locking primitives. It is therefore time to
enlist the aid of lockdep.
This set of patches is inspired by earlier work by Peter
Zijlstra and Thomas Gleixner, and takes the following approach:
o Set up separate lockdep classes for RCU, RCU-bh, and RCU-sched.
o Set up separate lockdep classes for each instance of SRCU.
o Create primitives that check for being in an RCU read-side
critical section. These return exact answers if lockdep is
fully enabled, but if unsure, report being in an RCU read-side
critical section. (We want to avoid false positives!)
The primitives are:
For RCU: rcu_read_lock_held(void)
For RCU-bh: rcu_read_lock_bh_held(void)
For RCU-sched: rcu_read_lock_sched_held(void)
For SRCU: srcu_read_lock_held(struct srcu_struct *sp)
o Add rcu_dereference_check(), which takes a second argument
in which one places a boolean expression based on the above
primitives and/or lockdep_is_held().
o A new kernel configuration parameter, CONFIG_PROVE_RCU, enables
rcu_dereference_check(). This depends on CONFIG_PROVE_LOCKING,
and should be quite helpful during the transition period while
CONFIG_PROVE_RCU-unaware patches are in flight.
The existing rcu_dereference() primitive does no checking, but
upcoming patches will change that.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: laijs@cn.fujitsu.com
Cc: dipankar@in.ibm.com
Cc: mathieu.desnoyers@polymtl.ca
Cc: josh@joshtriplett.org
Cc: dvhltc@us.ibm.com
Cc: niv@us.ibm.com
Cc: peterz@infradead.org
Cc: rostedt@goodmis.org
Cc: Valdis.Kletnieks@vt.edu
Cc: dhowells@redhat.com
LKML-Reference: <1266887105-1528-1-git-send-email-paulmck@linux.vnet.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2010-02-23 09:04:45 +08:00
|
|
|
p = rcu_dereference_check(rcu_torture_current,
|
|
|
|
rcu_read_lock_bh_held() ||
|
|
|
|
rcu_read_lock_sched_held() ||
|
|
|
|
srcu_read_lock_held(&srcu_ctl));
|
2008-06-26 03:24:52 +08:00
|
|
|
if (p == NULL) {
|
|
|
|
/* Leave because rcu_torture_writer is not yet underway */
|
|
|
|
cur_ops->readunlock(idx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (p->rtort_mbtest == 0)
|
|
|
|
atomic_inc(&n_rcu_torture_mberror);
|
|
|
|
spin_lock(&rand_lock);
|
2009-06-26 00:08:17 +08:00
|
|
|
cur_ops->read_delay(&rand);
|
2008-06-26 03:24:52 +08:00
|
|
|
n_rcu_torture_timers++;
|
|
|
|
spin_unlock(&rand_lock);
|
|
|
|
preempt_disable();
|
|
|
|
pipe_count = p->rtort_pipe_count;
|
|
|
|
if (pipe_count > RCU_TORTURE_PIPE_LEN) {
|
|
|
|
/* Should not happen, but... */
|
|
|
|
pipe_count = RCU_TORTURE_PIPE_LEN;
|
|
|
|
}
|
2012-11-15 08:26:40 +08:00
|
|
|
completed_end = cur_ops->completed();
|
|
|
|
if (pipe_count > 1) {
|
|
|
|
do_trace_rcu_torture_read(cur_ops->name, &p->rtort_rcu, ts,
|
|
|
|
completed, completed_end);
|
2011-10-02 22:44:32 +08:00
|
|
|
rcutorture_trace_dump();
|
2012-11-15 08:26:40 +08:00
|
|
|
}
|
2009-10-29 21:34:15 +08:00
|
|
|
__this_cpu_inc(rcu_torture_count[pipe_count]);
|
2012-11-15 08:26:40 +08:00
|
|
|
completed = completed_end - completed;
|
2008-06-26 03:24:52 +08:00
|
|
|
if (completed > RCU_TORTURE_PIPE_LEN) {
|
|
|
|
/* Should not happen, but... */
|
|
|
|
completed = RCU_TORTURE_PIPE_LEN;
|
|
|
|
}
|
2009-10-29 21:34:15 +08:00
|
|
|
__this_cpu_inc(rcu_torture_batch[completed]);
|
2008-06-26 03:24:52 +08:00
|
|
|
preempt_enable();
|
|
|
|
cur_ops->readunlock(idx);
|
|
|
|
}
|
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
/*
|
|
|
|
* RCU torture reader kthread. Repeatedly dereferences rcu_torture_current,
|
|
|
|
* incrementing the corresponding element of the pipeline array. The
|
|
|
|
* counter in the element should never be greater than 1, otherwise, the
|
|
|
|
* RCU implementation is broken.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
rcu_torture_reader(void *arg)
|
|
|
|
{
|
|
|
|
int completed;
|
2012-11-15 08:26:40 +08:00
|
|
|
int completed_end;
|
2006-06-27 17:54:03 +08:00
|
|
|
int idx;
|
2014-01-28 03:49:39 +08:00
|
|
|
DEFINE_TORTURE_RANDOM(rand);
|
2005-10-31 07:03:12 +08:00
|
|
|
struct rcu_torture *p;
|
|
|
|
int pipe_count;
|
2008-06-26 03:24:52 +08:00
|
|
|
struct timer_list t;
|
2012-11-15 08:26:40 +08:00
|
|
|
unsigned long long ts;
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_reader task started");
|
2014-02-11 15:34:49 +08:00
|
|
|
set_user_nice(current, MAX_NICE);
|
2009-06-26 00:08:17 +08:00
|
|
|
if (irqreader && cur_ops->irq_capable)
|
2008-06-26 03:24:52 +08:00
|
|
|
setup_timer_on_stack(&t, rcu_torture_timer, 0);
|
2005-11-14 08:07:22 +08:00
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
do {
|
2009-06-26 00:08:17 +08:00
|
|
|
if (irqreader && cur_ops->irq_capable) {
|
2008-06-26 03:24:52 +08:00
|
|
|
if (!timer_pending(&t))
|
2010-02-23 09:05:04 +08:00
|
|
|
mod_timer(&t, jiffies + 1);
|
2008-06-26 03:24:52 +08:00
|
|
|
}
|
2006-06-27 17:54:03 +08:00
|
|
|
idx = cur_ops->readlock();
|
|
|
|
completed = cur_ops->completed();
|
2013-02-05 02:36:13 +08:00
|
|
|
ts = rcu_trace_clock_local();
|
rcu: Introduce lockdep-based checking to RCU read-side primitives
Inspection is proving insufficient to catch all RCU misuses,
which is understandable given that rcu_dereference() might be
protected by any of four different flavors of RCU (RCU, RCU-bh,
RCU-sched, and SRCU), and might also/instead be protected by any
of a number of locking primitives. It is therefore time to
enlist the aid of lockdep.
This set of patches is inspired by earlier work by Peter
Zijlstra and Thomas Gleixner, and takes the following approach:
o Set up separate lockdep classes for RCU, RCU-bh, and RCU-sched.
o Set up separate lockdep classes for each instance of SRCU.
o Create primitives that check for being in an RCU read-side
critical section. These return exact answers if lockdep is
fully enabled, but if unsure, report being in an RCU read-side
critical section. (We want to avoid false positives!)
The primitives are:
For RCU: rcu_read_lock_held(void)
For RCU-bh: rcu_read_lock_bh_held(void)
For RCU-sched: rcu_read_lock_sched_held(void)
For SRCU: srcu_read_lock_held(struct srcu_struct *sp)
o Add rcu_dereference_check(), which takes a second argument
in which one places a boolean expression based on the above
primitives and/or lockdep_is_held().
o A new kernel configuration parameter, CONFIG_PROVE_RCU, enables
rcu_dereference_check(). This depends on CONFIG_PROVE_LOCKING,
and should be quite helpful during the transition period while
CONFIG_PROVE_RCU-unaware patches are in flight.
The existing rcu_dereference() primitive does no checking, but
upcoming patches will change that.
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Cc: laijs@cn.fujitsu.com
Cc: dipankar@in.ibm.com
Cc: mathieu.desnoyers@polymtl.ca
Cc: josh@joshtriplett.org
Cc: dvhltc@us.ibm.com
Cc: niv@us.ibm.com
Cc: peterz@infradead.org
Cc: rostedt@goodmis.org
Cc: Valdis.Kletnieks@vt.edu
Cc: dhowells@redhat.com
LKML-Reference: <1266887105-1528-1-git-send-email-paulmck@linux.vnet.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2010-02-23 09:04:45 +08:00
|
|
|
p = rcu_dereference_check(rcu_torture_current,
|
|
|
|
rcu_read_lock_bh_held() ||
|
|
|
|
rcu_read_lock_sched_held() ||
|
|
|
|
srcu_read_lock_held(&srcu_ctl));
|
2005-10-31 07:03:12 +08:00
|
|
|
if (p == NULL) {
|
|
|
|
/* Wait for rcu_torture_writer to get underway */
|
2006-06-27 17:54:03 +08:00
|
|
|
cur_ops->readunlock(idx);
|
2005-10-31 07:03:12 +08:00
|
|
|
schedule_timeout_interruptible(HZ);
|
|
|
|
continue;
|
|
|
|
}
|
2005-11-18 17:10:50 +08:00
|
|
|
if (p->rtort_mbtest == 0)
|
|
|
|
atomic_inc(&n_rcu_torture_mberror);
|
2009-06-26 00:08:17 +08:00
|
|
|
cur_ops->read_delay(&rand);
|
2005-10-31 07:03:12 +08:00
|
|
|
preempt_disable();
|
|
|
|
pipe_count = p->rtort_pipe_count;
|
|
|
|
if (pipe_count > RCU_TORTURE_PIPE_LEN) {
|
|
|
|
/* Should not happen, but... */
|
|
|
|
pipe_count = RCU_TORTURE_PIPE_LEN;
|
|
|
|
}
|
2012-11-15 08:26:40 +08:00
|
|
|
completed_end = cur_ops->completed();
|
|
|
|
if (pipe_count > 1) {
|
|
|
|
do_trace_rcu_torture_read(cur_ops->name, &p->rtort_rcu,
|
|
|
|
ts, completed, completed_end);
|
2011-10-02 22:44:32 +08:00
|
|
|
rcutorture_trace_dump();
|
2012-11-15 08:26:40 +08:00
|
|
|
}
|
2009-10-29 21:34:15 +08:00
|
|
|
__this_cpu_inc(rcu_torture_count[pipe_count]);
|
2012-11-15 08:26:40 +08:00
|
|
|
completed = completed_end - completed;
|
2005-10-31 07:03:12 +08:00
|
|
|
if (completed > RCU_TORTURE_PIPE_LEN) {
|
|
|
|
/* Should not happen, but... */
|
|
|
|
completed = RCU_TORTURE_PIPE_LEN;
|
|
|
|
}
|
2009-10-29 21:34:15 +08:00
|
|
|
__this_cpu_inc(rcu_torture_batch[completed]);
|
2005-10-31 07:03:12 +08:00
|
|
|
preempt_enable();
|
2006-06-27 17:54:03 +08:00
|
|
|
cur_ops->readunlock(idx);
|
2014-03-13 01:26:35 +08:00
|
|
|
cond_resched();
|
2014-02-01 03:57:43 +08:00
|
|
|
stutter_wait("rcu_torture_reader");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2014-03-23 23:58:27 +08:00
|
|
|
if (irqreader && cur_ops->irq_capable) {
|
2008-06-26 03:24:52 +08:00
|
|
|
del_timer_sync(&t);
|
2014-03-23 23:58:27 +08:00
|
|
|
destroy_timer_on_stack(&t);
|
|
|
|
}
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_reader");
|
2005-10-31 07:03:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-07-14 21:16:15 +08:00
|
|
|
* Print torture statistics. Caller must ensure that there is only
|
|
|
|
* one call to this function at a given time!!! This is normally
|
|
|
|
* accomplished by relying on the module system to only have one copy
|
|
|
|
* of the module loaded, and then by giving the rcu_torture_stats
|
|
|
|
* kthread full control (or the init/cleanup functions when rcu_torture_stats
|
|
|
|
* thread is not running).
|
2005-10-31 07:03:12 +08:00
|
|
|
*/
|
rcu/torture: Dynamically allocate SRCU output buffer to avoid overflow
If the rcutorture SRCU output exceeds 4096 bytes, for example, if you
have more than about 75 CPUs, it will overflow the current statically
allocated buffer. This commit therefore replaces this static buffer
with a dynamically buffer whose size is based on the number of CPUs.
Benefits:
- Avoids both buffer overflow and output truncation.
- Handles an arbitrarily large number of CPUs.
- Straightforward implementation.
Shortcomings:
- Some memory is wasted:
1 cpu now comsumes 50 - 60 bytes, and this patch provides 200 bytes.
Therefore, for 1K CPUs, roughly 100KB of memory will be wasted.
However, the memory is freed immediately after printing, so this
wastage should not be a problem in practice.
Testing (Fedora16 2 CPUs, 2GB RAM x86_64):
- as module, with/without "torture_type=srcu".
- build-in not boot runnable, with/without "torture_type=srcu".
- build-in let boot runnable, with/without "torture_type=srcu".
Signed-off-by: Chen Gang <gang.chen@asianux.com>
Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
2013-11-07 10:30:25 +08:00
|
|
|
static void
|
2014-07-14 21:16:15 +08:00
|
|
|
rcu_torture_stats_print(void)
|
2005-10-31 07:03:12 +08:00
|
|
|
{
|
|
|
|
int cpu;
|
|
|
|
int i;
|
|
|
|
long pipesummary[RCU_TORTURE_PIPE_LEN + 1] = { 0 };
|
|
|
|
long batchsummary[RCU_TORTURE_PIPE_LEN + 1] = { 0 };
|
2014-02-20 02:51:42 +08:00
|
|
|
static unsigned long rtcv_snap = ULONG_MAX;
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2006-03-28 17:56:37 +08:00
|
|
|
for_each_possible_cpu(cpu) {
|
2005-10-31 07:03:12 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) {
|
|
|
|
pipesummary[i] += per_cpu(rcu_torture_count, cpu)[i];
|
|
|
|
batchsummary[i] += per_cpu(rcu_torture_batch, cpu)[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = RCU_TORTURE_PIPE_LEN - 1; i >= 0; i--) {
|
|
|
|
if (pipesummary[i] != 0)
|
|
|
|
break;
|
|
|
|
}
|
2014-07-14 21:16:15 +08:00
|
|
|
|
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG);
|
|
|
|
pr_cont("rtc: %p ver: %lu tfle: %d rta: %d rtaf: %d rtf: %d ",
|
|
|
|
rcu_torture_current,
|
|
|
|
rcu_torture_current_version,
|
|
|
|
list_empty(&rcu_torture_freelist),
|
|
|
|
atomic_read(&n_rcu_torture_alloc),
|
|
|
|
atomic_read(&n_rcu_torture_alloc_fail),
|
|
|
|
atomic_read(&n_rcu_torture_free));
|
|
|
|
pr_cont("rtmbe: %d rtbke: %ld rtbre: %ld ",
|
|
|
|
atomic_read(&n_rcu_torture_mberror),
|
|
|
|
n_rcu_torture_boost_ktrerror,
|
|
|
|
n_rcu_torture_boost_rterror);
|
|
|
|
pr_cont("rtbf: %ld rtb: %ld nt: %ld ",
|
|
|
|
n_rcu_torture_boost_failure,
|
|
|
|
n_rcu_torture_boosts,
|
|
|
|
n_rcu_torture_timers);
|
|
|
|
torture_onoff_stats();
|
|
|
|
pr_cont("barrier: %ld/%ld:%ld\n",
|
|
|
|
n_barrier_successes,
|
|
|
|
n_barrier_attempts,
|
|
|
|
n_rcu_torture_barrier_error);
|
|
|
|
|
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG);
|
2010-09-03 07:16:14 +08:00
|
|
|
if (atomic_read(&n_rcu_torture_mberror) != 0 ||
|
2012-02-21 09:51:45 +08:00
|
|
|
n_rcu_torture_barrier_error != 0 ||
|
2010-09-03 07:16:14 +08:00
|
|
|
n_rcu_torture_boost_ktrerror != 0 ||
|
|
|
|
n_rcu_torture_boost_rterror != 0 ||
|
2012-02-21 09:51:45 +08:00
|
|
|
n_rcu_torture_boost_failure != 0 ||
|
|
|
|
i > 1) {
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont("%s", "!!! ");
|
2005-11-18 17:10:50 +08:00
|
|
|
atomic_inc(&n_rcu_torture_error);
|
2008-06-18 16:09:48 +08:00
|
|
|
WARN_ON_ONCE(1);
|
2005-11-18 17:10:50 +08:00
|
|
|
}
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont("Reader Pipe: ");
|
2005-10-31 07:03:12 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++)
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont(" %ld", pipesummary[i]);
|
|
|
|
pr_cont("\n");
|
|
|
|
|
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG);
|
|
|
|
pr_cont("Reader Batch: ");
|
2006-06-27 17:54:03 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++)
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont(" %ld", batchsummary[i]);
|
|
|
|
pr_cont("\n");
|
|
|
|
|
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG);
|
|
|
|
pr_cont("Free-Block Circulation: ");
|
2005-10-31 07:03:12 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) {
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont(" %d", atomic_read(&rcu_torture_wcount[i]));
|
2005-10-31 07:03:12 +08:00
|
|
|
}
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_cont("\n");
|
|
|
|
|
2007-05-08 15:33:20 +08:00
|
|
|
if (cur_ops->stats)
|
2014-07-14 21:16:15 +08:00
|
|
|
cur_ops->stats();
|
2014-02-20 02:51:42 +08:00
|
|
|
if (rtcv_snap == rcu_torture_current_version &&
|
|
|
|
rcu_torture_current != NULL) {
|
|
|
|
int __maybe_unused flags;
|
|
|
|
unsigned long __maybe_unused gpnum;
|
|
|
|
unsigned long __maybe_unused completed;
|
|
|
|
|
|
|
|
rcutorture_get_gp_data(cur_ops->ttype,
|
|
|
|
&flags, &gpnum, &completed);
|
2014-07-14 21:16:15 +08:00
|
|
|
pr_alert("??? Writer stall state %d g%lu c%lu f%#x\n",
|
|
|
|
rcu_torture_writer_state,
|
|
|
|
gpnum, completed, flags);
|
2014-03-12 22:10:41 +08:00
|
|
|
show_rcu_gp_kthreads();
|
2014-03-08 06:15:28 +08:00
|
|
|
rcutorture_trace_dump();
|
2014-02-20 02:51:42 +08:00
|
|
|
}
|
|
|
|
rtcv_snap = rcu_torture_current_version;
|
2005-10-31 07:03:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Periodically prints torture statistics, if periodic statistics printing
|
|
|
|
* was specified via the stat_interval module parameter.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
rcu_torture_stats(void *arg)
|
|
|
|
{
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_stats task started");
|
2005-10-31 07:03:12 +08:00
|
|
|
do {
|
|
|
|
schedule_timeout_interruptible(stat_interval * HZ);
|
|
|
|
rcu_torture_stats_print();
|
2014-01-29 23:40:27 +08:00
|
|
|
torture_shutdown_absorb("rcu_torture_stats");
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_stats");
|
2008-06-18 20:21:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-03-24 19:15:58 +08:00
|
|
|
static inline void
|
2013-07-13 04:50:28 +08:00
|
|
|
rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag)
|
2006-03-24 19:15:58 +08:00
|
|
|
{
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("%s" TORTURE_FLAG
|
|
|
|
"--- %s: nreaders=%d nfakewriters=%d "
|
|
|
|
"stat_interval=%d verbose=%d test_no_idle_hz=%d "
|
|
|
|
"shuffle_interval=%d stutter=%d irqreader=%d "
|
|
|
|
"fqs_duration=%d fqs_holdoff=%d fqs_stutter=%d "
|
|
|
|
"test_boost=%d/%d test_boost_interval=%d "
|
|
|
|
"test_boost_duration=%d shutdown_secs=%d "
|
2012-10-21 03:56:06 +08:00
|
|
|
"stall_cpu=%d stall_cpu_holdoff=%d "
|
|
|
|
"n_barrier_cbs=%d "
|
2012-08-10 07:30:45 +08:00
|
|
|
"onoff_interval=%d onoff_holdoff=%d\n",
|
|
|
|
torture_type, tag, nrealreaders, nfakewriters,
|
|
|
|
stat_interval, verbose, test_no_idle_hz, shuffle_interval,
|
|
|
|
stutter, irqreader, fqs_duration, fqs_holdoff, fqs_stutter,
|
|
|
|
test_boost, cur_ops->can_boost,
|
|
|
|
test_boost_interval, test_boost_duration, shutdown_secs,
|
2012-10-21 03:56:06 +08:00
|
|
|
stall_cpu, stall_cpu_holdoff,
|
|
|
|
n_barrier_cbs,
|
2012-08-10 07:30:45 +08:00
|
|
|
onoff_interval, onoff_holdoff);
|
2006-03-24 19:15:58 +08:00
|
|
|
}
|
|
|
|
|
2010-09-03 07:16:14 +08:00
|
|
|
static void rcutorture_booster_cleanup(int cpu)
|
|
|
|
{
|
|
|
|
struct task_struct *t;
|
|
|
|
|
|
|
|
if (boost_tasks[cpu] == NULL)
|
|
|
|
return;
|
|
|
|
mutex_lock(&boost_mutex);
|
|
|
|
t = boost_tasks[cpu];
|
|
|
|
boost_tasks[cpu] = NULL;
|
|
|
|
mutex_unlock(&boost_mutex);
|
|
|
|
|
|
|
|
/* This must be outside of the mutex, otherwise deadlock! */
|
2014-02-05 03:47:08 +08:00
|
|
|
torture_stop_kthread(rcu_torture_boost, t);
|
2010-09-03 07:16:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int rcutorture_booster_init(int cpu)
|
|
|
|
{
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
if (boost_tasks[cpu] != NULL)
|
|
|
|
return 0; /* Already created, nothing more to do. */
|
|
|
|
|
|
|
|
/* Don't allow time recalculation while creating a new task. */
|
|
|
|
mutex_lock(&boost_mutex);
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("Creating rcu_torture_boost task");
|
2011-06-17 06:53:18 +08:00
|
|
|
boost_tasks[cpu] = kthread_create_on_node(rcu_torture_boost, NULL,
|
|
|
|
cpu_to_node(cpu),
|
|
|
|
"rcu_torture_boost");
|
2010-09-03 07:16:14 +08:00
|
|
|
if (IS_ERR(boost_tasks[cpu])) {
|
|
|
|
retval = PTR_ERR(boost_tasks[cpu]);
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_boost task create failed");
|
2010-09-03 07:16:14 +08:00
|
|
|
n_rcu_torture_boost_ktrerror++;
|
|
|
|
boost_tasks[cpu] = NULL;
|
|
|
|
mutex_unlock(&boost_mutex);
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
kthread_bind(boost_tasks[cpu], cpu);
|
|
|
|
wake_up_process(boost_tasks[cpu]);
|
|
|
|
mutex_unlock(&boost_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-21 07:36:33 +08:00
|
|
|
/*
|
|
|
|
* CPU-stall kthread. It waits as specified by stall_cpu_holdoff, then
|
|
|
|
* induces a CPU stall for the time specified by stall_cpu.
|
|
|
|
*/
|
2013-06-20 02:52:21 +08:00
|
|
|
static int rcu_torture_stall(void *args)
|
2012-01-21 07:36:33 +08:00
|
|
|
{
|
|
|
|
unsigned long stop_at;
|
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_stall task started");
|
2012-01-21 07:36:33 +08:00
|
|
|
if (stall_cpu_holdoff > 0) {
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_stall begin holdoff");
|
2012-01-21 07:36:33 +08:00
|
|
|
schedule_timeout_interruptible(stall_cpu_holdoff * HZ);
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_stall end holdoff");
|
2012-01-21 07:36:33 +08:00
|
|
|
}
|
|
|
|
if (!kthread_should_stop()) {
|
|
|
|
stop_at = get_seconds() + stall_cpu;
|
|
|
|
/* RCU CPU stall is expected behavior in following code. */
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("rcu_torture_stall start.\n");
|
2012-01-21 07:36:33 +08:00
|
|
|
rcu_read_lock();
|
|
|
|
preempt_disable();
|
|
|
|
while (ULONG_CMP_LT(get_seconds(), stop_at))
|
|
|
|
continue; /* Induce RCU CPU stall warning. */
|
|
|
|
preempt_enable();
|
|
|
|
rcu_read_unlock();
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("rcu_torture_stall end.\n");
|
2012-01-21 07:36:33 +08:00
|
|
|
}
|
2014-01-29 23:40:27 +08:00
|
|
|
torture_shutdown_absorb("rcu_torture_stall");
|
2012-01-21 07:36:33 +08:00
|
|
|
while (!kthread_should_stop())
|
|
|
|
schedule_timeout_interruptible(10 * HZ);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Spawn CPU-stall kthread, if stall_cpu specified. */
|
|
|
|
static int __init rcu_torture_stall_init(void)
|
|
|
|
{
|
|
|
|
if (stall_cpu <= 0)
|
|
|
|
return 0;
|
2014-02-04 03:52:27 +08:00
|
|
|
return torture_create_kthread(rcu_torture_stall, NULL, stall_task);
|
2012-01-21 07:36:33 +08:00
|
|
|
}
|
|
|
|
|
2012-02-21 09:51:45 +08:00
|
|
|
/* Callback function for RCU barrier testing. */
|
2014-02-27 19:46:57 +08:00
|
|
|
static void rcu_torture_barrier_cbf(struct rcu_head *rcu)
|
2012-02-21 09:51:45 +08:00
|
|
|
{
|
|
|
|
atomic_inc(&barrier_cbs_invoked);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* kthread function to register callbacks used to test RCU barriers. */
|
|
|
|
static int rcu_torture_barrier_cbs(void *arg)
|
|
|
|
{
|
|
|
|
long myid = (long)arg;
|
2012-05-29 10:21:41 +08:00
|
|
|
bool lastphase = 0;
|
2013-09-25 06:04:06 +08:00
|
|
|
bool newphase;
|
2012-02-21 09:51:45 +08:00
|
|
|
struct rcu_head rcu;
|
|
|
|
|
|
|
|
init_rcu_head_on_stack(&rcu);
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_barrier_cbs task started");
|
2014-02-11 15:34:49 +08:00
|
|
|
set_user_nice(current, MAX_NICE);
|
2012-02-21 09:51:45 +08:00
|
|
|
do {
|
|
|
|
wait_event(barrier_cbs_wq[myid],
|
2013-09-25 06:04:06 +08:00
|
|
|
(newphase =
|
|
|
|
ACCESS_ONCE(barrier_phase)) != lastphase ||
|
2014-01-31 07:49:29 +08:00
|
|
|
torture_must_stop());
|
2013-09-25 06:04:06 +08:00
|
|
|
lastphase = newphase;
|
2012-05-29 10:21:41 +08:00
|
|
|
smp_mb(); /* ensure barrier_phase load before ->call(). */
|
2014-01-31 07:49:29 +08:00
|
|
|
if (torture_must_stop())
|
2012-02-21 09:51:45 +08:00
|
|
|
break;
|
|
|
|
cur_ops->call(&rcu, rcu_torture_barrier_cbf);
|
|
|
|
if (atomic_dec_and_test(&barrier_cbs_count))
|
|
|
|
wake_up(&barrier_wq);
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2012-02-21 09:51:45 +08:00
|
|
|
cur_ops->cb_barrier();
|
|
|
|
destroy_rcu_head_on_stack(&rcu);
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_barrier_cbs");
|
2012-02-21 09:51:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* kthread function to drive and coordinate RCU barrier testing. */
|
|
|
|
static int rcu_torture_barrier(void *arg)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_barrier task starting");
|
2012-02-21 09:51:45 +08:00
|
|
|
do {
|
|
|
|
atomic_set(&barrier_cbs_invoked, 0);
|
|
|
|
atomic_set(&barrier_cbs_count, n_barrier_cbs);
|
2012-05-29 10:21:41 +08:00
|
|
|
smp_mb(); /* Ensure barrier_phase after prior assignments. */
|
|
|
|
barrier_phase = !barrier_phase;
|
2012-02-21 09:51:45 +08:00
|
|
|
for (i = 0; i < n_barrier_cbs; i++)
|
|
|
|
wake_up(&barrier_cbs_wq[i]);
|
|
|
|
wait_event(barrier_wq,
|
|
|
|
atomic_read(&barrier_cbs_count) == 0 ||
|
2014-01-31 07:49:29 +08:00
|
|
|
torture_must_stop());
|
|
|
|
if (torture_must_stop())
|
2012-02-21 09:51:45 +08:00
|
|
|
break;
|
|
|
|
n_barrier_attempts++;
|
2013-09-25 06:04:06 +08:00
|
|
|
cur_ops->cb_barrier(); /* Implies smp_mb() for wait_event(). */
|
2012-02-21 09:51:45 +08:00
|
|
|
if (atomic_read(&barrier_cbs_invoked) != n_barrier_cbs) {
|
|
|
|
n_rcu_torture_barrier_error++;
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
}
|
|
|
|
n_barrier_successes++;
|
|
|
|
schedule_timeout_interruptible(HZ / 10);
|
2014-01-31 07:49:29 +08:00
|
|
|
} while (!torture_must_stop());
|
2014-02-01 09:37:28 +08:00
|
|
|
torture_kthread_stopping("rcu_torture_barrier");
|
2012-02-21 09:51:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize RCU barrier testing. */
|
|
|
|
static int rcu_torture_barrier_init(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (n_barrier_cbs == 0)
|
|
|
|
return 0;
|
|
|
|
if (cur_ops->call == NULL || cur_ops->cb_barrier == NULL) {
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("%s" TORTURE_FLAG
|
|
|
|
" Call or barrier ops missing for %s,\n",
|
|
|
|
torture_type, cur_ops->name);
|
|
|
|
pr_alert("%s" TORTURE_FLAG
|
|
|
|
" RCU barrier testing omitted from run.\n",
|
|
|
|
torture_type);
|
2012-02-21 09:51:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
atomic_set(&barrier_cbs_count, 0);
|
|
|
|
atomic_set(&barrier_cbs_invoked, 0);
|
|
|
|
barrier_cbs_tasks =
|
|
|
|
kzalloc(n_barrier_cbs * sizeof(barrier_cbs_tasks[0]),
|
|
|
|
GFP_KERNEL);
|
|
|
|
barrier_cbs_wq =
|
|
|
|
kzalloc(n_barrier_cbs * sizeof(barrier_cbs_wq[0]),
|
|
|
|
GFP_KERNEL);
|
2012-12-21 03:11:28 +08:00
|
|
|
if (barrier_cbs_tasks == NULL || !barrier_cbs_wq)
|
2012-02-21 09:51:45 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < n_barrier_cbs; i++) {
|
|
|
|
init_waitqueue_head(&barrier_cbs_wq[i]);
|
2014-02-04 03:52:27 +08:00
|
|
|
ret = torture_create_kthread(rcu_torture_barrier_cbs,
|
|
|
|
(void *)(long)i,
|
|
|
|
barrier_cbs_tasks[i]);
|
|
|
|
if (ret)
|
2012-02-21 09:51:45 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2014-02-04 03:52:27 +08:00
|
|
|
return torture_create_kthread(rcu_torture_barrier, NULL, barrier_task);
|
2012-02-21 09:51:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Clean up after RCU barrier testing. */
|
|
|
|
static void rcu_torture_barrier_cleanup(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2014-02-05 03:47:08 +08:00
|
|
|
torture_stop_kthread(rcu_torture_barrier, barrier_task);
|
2012-02-21 09:51:45 +08:00
|
|
|
if (barrier_cbs_tasks != NULL) {
|
2014-02-05 03:47:08 +08:00
|
|
|
for (i = 0; i < n_barrier_cbs; i++)
|
|
|
|
torture_stop_kthread(rcu_torture_barrier_cbs,
|
|
|
|
barrier_cbs_tasks[i]);
|
2012-02-21 09:51:45 +08:00
|
|
|
kfree(barrier_cbs_tasks);
|
|
|
|
barrier_cbs_tasks = NULL;
|
|
|
|
}
|
|
|
|
if (barrier_cbs_wq != NULL) {
|
|
|
|
kfree(barrier_cbs_wq);
|
|
|
|
barrier_cbs_wq = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-09-03 07:16:14 +08:00
|
|
|
static int rcutorture_cpu_notify(struct notifier_block *self,
|
|
|
|
unsigned long action, void *hcpu)
|
|
|
|
{
|
|
|
|
long cpu = (long)hcpu;
|
|
|
|
|
|
|
|
switch (action) {
|
|
|
|
case CPU_ONLINE:
|
|
|
|
case CPU_DOWN_FAILED:
|
|
|
|
(void)rcutorture_booster_init(cpu);
|
|
|
|
break;
|
|
|
|
case CPU_DOWN_PREPARE:
|
|
|
|
rcutorture_booster_cleanup(cpu);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block rcutorture_cpu_nb = {
|
|
|
|
.notifier_call = rcutorture_cpu_notify,
|
|
|
|
};
|
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
static void
|
|
|
|
rcu_torture_cleanup(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
2011-04-04 12:33:51 +08:00
|
|
|
rcutorture_record_test_transition();
|
2014-01-31 06:21:11 +08:00
|
|
|
if (torture_cleanup()) {
|
2008-12-16 08:13:07 +08:00
|
|
|
if (cur_ops->cb_barrier != NULL)
|
|
|
|
cur_ops->cb_barrier();
|
|
|
|
return;
|
|
|
|
}
|
2006-01-08 17:03:42 +08:00
|
|
|
|
2012-02-21 09:51:45 +08:00
|
|
|
rcu_torture_barrier_cleanup();
|
2014-02-05 03:47:08 +08:00
|
|
|
torture_stop_kthread(rcu_torture_stall, stall_task);
|
|
|
|
torture_stop_kthread(rcu_torture_writer, writer_task);
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2007-05-08 15:33:20 +08:00
|
|
|
if (reader_tasks) {
|
2014-02-05 03:47:08 +08:00
|
|
|
for (i = 0; i < nrealreaders; i++)
|
|
|
|
torture_stop_kthread(rcu_torture_reader,
|
|
|
|
reader_tasks[i]);
|
2005-10-31 07:03:12 +08:00
|
|
|
kfree(reader_tasks);
|
|
|
|
}
|
|
|
|
rcu_torture_current = NULL;
|
|
|
|
|
2007-05-08 15:33:20 +08:00
|
|
|
if (fakewriter_tasks) {
|
2006-10-04 17:17:13 +08:00
|
|
|
for (i = 0; i < nfakewriters; i++) {
|
2014-02-05 03:47:08 +08:00
|
|
|
torture_stop_kthread(rcu_torture_fakewriter,
|
|
|
|
fakewriter_tasks[i]);
|
2006-10-04 17:17:13 +08:00
|
|
|
}
|
|
|
|
kfree(fakewriter_tasks);
|
|
|
|
fakewriter_tasks = NULL;
|
|
|
|
}
|
|
|
|
|
2014-02-05 03:47:08 +08:00
|
|
|
torture_stop_kthread(rcu_torture_stats, stats_task);
|
|
|
|
torture_stop_kthread(rcu_torture_fqs, fqs_task);
|
2010-09-03 07:16:14 +08:00
|
|
|
if ((test_boost == 1 && cur_ops->can_boost) ||
|
|
|
|
test_boost == 2) {
|
|
|
|
unregister_cpu_notifier(&rcutorture_cpu_nb);
|
|
|
|
for_each_possible_cpu(i)
|
|
|
|
rcutorture_booster_cleanup(i);
|
|
|
|
}
|
2010-01-05 07:09:10 +08:00
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
/* Wait for all RCU callbacks to fire. */
|
2008-05-13 03:21:05 +08:00
|
|
|
|
|
|
|
if (cur_ops->cb_barrier != NULL)
|
|
|
|
cur_ops->cb_barrier();
|
2005-10-31 07:03:12 +08:00
|
|
|
|
|
|
|
rcu_torture_stats_print(); /* -After- the stats thread is stopped! */
|
2006-06-27 17:54:03 +08:00
|
|
|
|
2012-02-21 09:51:45 +08:00
|
|
|
if (atomic_read(&n_rcu_torture_error) || n_rcu_torture_barrier_error)
|
2010-09-03 07:16:14 +08:00
|
|
|
rcu_torture_print_module_parms(cur_ops, "End of test: FAILURE");
|
2014-01-29 07:58:22 +08:00
|
|
|
else if (torture_onoff_failures())
|
2012-01-11 04:51:14 +08:00
|
|
|
rcu_torture_print_module_parms(cur_ops,
|
|
|
|
"End of test: RCU_HOTPLUG");
|
2006-03-24 19:15:58 +08:00
|
|
|
else
|
2010-09-03 07:16:14 +08:00
|
|
|
rcu_torture_print_module_parms(cur_ops, "End of test: SUCCESS");
|
2005-10-31 07:03:12 +08:00
|
|
|
}
|
|
|
|
|
2013-04-24 08:05:42 +08:00
|
|
|
#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD
|
|
|
|
static void rcu_torture_leak_cb(struct rcu_head *rhp)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rcu_torture_err_cb(struct rcu_head *rhp)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* This -might- happen due to race conditions, but is unlikely.
|
|
|
|
* The scenario that leads to this happening is that the
|
|
|
|
* first of the pair of duplicate callbacks is queued,
|
|
|
|
* someone else starts a grace period that includes that
|
|
|
|
* callback, then the second of the pair must wait for the
|
|
|
|
* next grace period. Unlikely, but can happen. If it
|
|
|
|
* does happen, the debug-objects subsystem won't have splatted.
|
|
|
|
*/
|
|
|
|
pr_alert("rcutorture: duplicated callback was invoked.\n");
|
|
|
|
}
|
|
|
|
#endif /* #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify that double-free causes debug-objects to complain, but only
|
|
|
|
* if CONFIG_DEBUG_OBJECTS_RCU_HEAD=y. Otherwise, say that the test
|
|
|
|
* cannot be carried out.
|
|
|
|
*/
|
|
|
|
static void rcu_test_debug_objects(void)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD
|
|
|
|
struct rcu_head rh1;
|
|
|
|
struct rcu_head rh2;
|
|
|
|
|
|
|
|
init_rcu_head_on_stack(&rh1);
|
|
|
|
init_rcu_head_on_stack(&rh2);
|
|
|
|
pr_alert("rcutorture: WARN: Duplicate call_rcu() test starting.\n");
|
|
|
|
|
|
|
|
/* Try to queue the rh2 pair of callbacks for the same grace period. */
|
|
|
|
preempt_disable(); /* Prevent preemption from interrupting test. */
|
|
|
|
rcu_read_lock(); /* Make it impossible to finish a grace period. */
|
|
|
|
call_rcu(&rh1, rcu_torture_leak_cb); /* Start grace period. */
|
|
|
|
local_irq_disable(); /* Make it harder to start a new grace period. */
|
|
|
|
call_rcu(&rh2, rcu_torture_leak_cb);
|
|
|
|
call_rcu(&rh2, rcu_torture_err_cb); /* Duplicate callback. */
|
|
|
|
local_irq_enable();
|
|
|
|
rcu_read_unlock();
|
|
|
|
preempt_enable();
|
|
|
|
|
|
|
|
/* Wait for them all to get done so we can safely return. */
|
|
|
|
rcu_barrier();
|
|
|
|
pr_alert("rcutorture: WARN: Duplicate call_rcu() test complete.\n");
|
|
|
|
destroy_rcu_head_on_stack(&rh1);
|
|
|
|
destroy_rcu_head_on_stack(&rh2);
|
|
|
|
#else /* #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD */
|
|
|
|
pr_alert("rcutorture: !CONFIG_DEBUG_OBJECTS_RCU_HEAD, not testing duplicate call_rcu()\n");
|
|
|
|
#endif /* #else #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD */
|
|
|
|
}
|
|
|
|
|
2007-05-08 15:25:24 +08:00
|
|
|
static int __init
|
2005-10-31 07:03:12 +08:00
|
|
|
rcu_torture_init(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int cpu;
|
|
|
|
int firsterr = 0;
|
2013-06-13 06:12:21 +08:00
|
|
|
static struct rcu_torture_ops *torture_ops[] = {
|
2014-02-07 00:45:56 +08:00
|
|
|
&rcu_ops, &rcu_bh_ops, &rcu_busted_ops, &srcu_ops, &sched_ops,
|
2013-06-13 06:12:21 +08:00
|
|
|
};
|
2005-10-31 07:03:12 +08:00
|
|
|
|
2014-04-08 00:14:11 +08:00
|
|
|
if (!torture_init_begin(torture_type, verbose, &rcutorture_runnable))
|
|
|
|
return -EBUSY;
|
2008-12-16 08:13:07 +08:00
|
|
|
|
2005-10-31 07:03:12 +08:00
|
|
|
/* Process args and tell the world that the torturer is on the job. */
|
2007-05-08 15:33:22 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(torture_ops); i++) {
|
2006-06-27 17:54:03 +08:00
|
|
|
cur_ops = torture_ops[i];
|
2007-05-08 15:33:22 +08:00
|
|
|
if (strcmp(torture_type, cur_ops->name) == 0)
|
2006-06-27 17:54:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2007-05-08 15:33:22 +08:00
|
|
|
if (i == ARRAY_SIZE(torture_ops)) {
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("rcu-torture: invalid torture type: \"%s\"\n",
|
|
|
|
torture_type);
|
|
|
|
pr_alert("rcu-torture types:");
|
2009-10-26 10:03:54 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(torture_ops); i++)
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert(" %s", torture_ops[i]->name);
|
|
|
|
pr_alert("\n");
|
2014-01-31 05:38:09 +08:00
|
|
|
torture_init_end();
|
2009-09-19 01:28:19 +08:00
|
|
|
return -EINVAL;
|
2006-06-27 17:54:03 +08:00
|
|
|
}
|
2010-01-05 07:09:10 +08:00
|
|
|
if (cur_ops->fqs == NULL && fqs_duration != 0) {
|
2012-08-10 07:30:45 +08:00
|
|
|
pr_alert("rcu-torture: ->fqs NULL and non-zero fqs_duration, fqs disabled.\n");
|
2010-01-05 07:09:10 +08:00
|
|
|
fqs_duration = 0;
|
|
|
|
}
|
2007-05-08 15:33:20 +08:00
|
|
|
if (cur_ops->init)
|
2006-06-27 17:54:03 +08:00
|
|
|
cur_ops->init(); /* no "goto unwind" prior to this point!!! */
|
|
|
|
|
2014-03-13 01:26:35 +08:00
|
|
|
if (nreaders >= 0) {
|
2005-10-31 07:03:12 +08:00
|
|
|
nrealreaders = nreaders;
|
2014-03-13 01:26:35 +08:00
|
|
|
} else {
|
|
|
|
nrealreaders = num_online_cpus() - 1;
|
|
|
|
if (nrealreaders <= 0)
|
|
|
|
nrealreaders = 1;
|
|
|
|
}
|
2010-09-03 07:16:14 +08:00
|
|
|
rcu_torture_print_module_parms(cur_ops, "Start of test");
|
2005-10-31 07:03:12 +08:00
|
|
|
|
|
|
|
/* Set up the freelist. */
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&rcu_torture_freelist);
|
2007-05-08 15:33:14 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(rcu_tortures); i++) {
|
2005-11-18 17:10:50 +08:00
|
|
|
rcu_tortures[i].rtort_mbtest = 0;
|
2005-10-31 07:03:12 +08:00
|
|
|
list_add_tail(&rcu_tortures[i].rtort_free,
|
|
|
|
&rcu_torture_freelist);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize the statistics so that each run gets its own numbers. */
|
|
|
|
|
|
|
|
rcu_torture_current = NULL;
|
|
|
|
rcu_torture_current_version = 0;
|
|
|
|
atomic_set(&n_rcu_torture_alloc, 0);
|
|
|
|
atomic_set(&n_rcu_torture_alloc_fail, 0);
|
|
|
|
atomic_set(&n_rcu_torture_free, 0);
|
2005-11-18 17:10:50 +08:00
|
|
|
atomic_set(&n_rcu_torture_mberror, 0);
|
|
|
|
atomic_set(&n_rcu_torture_error, 0);
|
2012-02-21 09:51:45 +08:00
|
|
|
n_rcu_torture_barrier_error = 0;
|
2010-09-03 07:16:14 +08:00
|
|
|
n_rcu_torture_boost_ktrerror = 0;
|
|
|
|
n_rcu_torture_boost_rterror = 0;
|
|
|
|
n_rcu_torture_boost_failure = 0;
|
|
|
|
n_rcu_torture_boosts = 0;
|
2005-10-31 07:03:12 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++)
|
|
|
|
atomic_set(&rcu_torture_wcount[i], 0);
|
2006-03-28 17:56:37 +08:00
|
|
|
for_each_possible_cpu(cpu) {
|
2005-10-31 07:03:12 +08:00
|
|
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) {
|
|
|
|
per_cpu(rcu_torture_count, cpu)[i] = 0;
|
|
|
|
per_cpu(rcu_torture_batch, cpu)[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Start up the kthreads. */
|
|
|
|
|
2014-02-04 03:52:27 +08:00
|
|
|
firsterr = torture_create_kthread(rcu_torture_writer, NULL,
|
|
|
|
writer_task);
|
|
|
|
if (firsterr)
|
2005-10-31 07:03:12 +08:00
|
|
|
goto unwind;
|
2006-10-04 17:17:13 +08:00
|
|
|
fakewriter_tasks = kzalloc(nfakewriters * sizeof(fakewriter_tasks[0]),
|
2009-09-19 01:28:19 +08:00
|
|
|
GFP_KERNEL);
|
2006-10-04 17:17:13 +08:00
|
|
|
if (fakewriter_tasks == NULL) {
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_ERRSTRING("out of memory");
|
2006-10-04 17:17:13 +08:00
|
|
|
firsterr = -ENOMEM;
|
|
|
|
goto unwind;
|
|
|
|
}
|
|
|
|
for (i = 0; i < nfakewriters; i++) {
|
2014-02-04 03:52:27 +08:00
|
|
|
firsterr = torture_create_kthread(rcu_torture_fakewriter,
|
|
|
|
NULL, fakewriter_tasks[i]);
|
|
|
|
if (firsterr)
|
2006-10-04 17:17:13 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
2006-10-04 17:17:11 +08:00
|
|
|
reader_tasks = kzalloc(nrealreaders * sizeof(reader_tasks[0]),
|
2005-10-31 07:03:12 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (reader_tasks == NULL) {
|
2014-01-29 23:25:25 +08:00
|
|
|
VERBOSE_TOROUT_ERRSTRING("out of memory");
|
2005-10-31 07:03:12 +08:00
|
|
|
firsterr = -ENOMEM;
|
|
|
|
goto unwind;
|
|
|
|
}
|
|
|
|
for (i = 0; i < nrealreaders; i++) {
|
2014-02-04 03:52:27 +08:00
|
|
|
firsterr = torture_create_kthread(rcu_torture_reader, NULL,
|
|
|
|
reader_tasks[i]);
|
|
|
|
if (firsterr)
|
2005-10-31 07:03:12 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
|
|
|
if (stat_interval > 0) {
|
2014-02-04 03:52:27 +08:00
|
|
|
firsterr = torture_create_kthread(rcu_torture_stats, NULL,
|
|
|
|
stats_task);
|
|
|
|
if (firsterr)
|
2005-10-31 07:03:12 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
2006-01-08 17:03:42 +08:00
|
|
|
if (test_no_idle_hz) {
|
2014-01-29 07:29:21 +08:00
|
|
|
firsterr = torture_shuffle_init(shuffle_interval * HZ);
|
|
|
|
if (firsterr)
|
2006-01-08 17:03:42 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
2008-06-18 20:21:44 +08:00
|
|
|
if (stutter < 0)
|
|
|
|
stutter = 0;
|
|
|
|
if (stutter) {
|
2014-02-01 03:57:43 +08:00
|
|
|
firsterr = torture_stutter_init(stutter * HZ);
|
|
|
|
if (firsterr)
|
2008-06-18 20:21:44 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
2010-01-05 07:09:10 +08:00
|
|
|
if (fqs_duration < 0)
|
|
|
|
fqs_duration = 0;
|
|
|
|
if (fqs_duration) {
|
2014-02-01 03:57:43 +08:00
|
|
|
/* Create the fqs thread */
|
2014-03-18 11:56:45 +08:00
|
|
|
firsterr = torture_create_kthread(rcu_torture_fqs, NULL,
|
|
|
|
fqs_task);
|
2014-02-04 03:52:27 +08:00
|
|
|
if (firsterr)
|
2010-01-05 07:09:10 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
2010-09-03 07:16:14 +08:00
|
|
|
if (test_boost_interval < 1)
|
|
|
|
test_boost_interval = 1;
|
|
|
|
if (test_boost_duration < 2)
|
|
|
|
test_boost_duration = 2;
|
|
|
|
if ((test_boost == 1 && cur_ops->can_boost) ||
|
|
|
|
test_boost == 2) {
|
|
|
|
|
|
|
|
boost_starttime = jiffies + test_boost_interval * HZ;
|
|
|
|
register_cpu_notifier(&rcutorture_cpu_nb);
|
|
|
|
for_each_possible_cpu(i) {
|
|
|
|
if (cpu_is_offline(i))
|
|
|
|
continue; /* Heuristic: CPU can go offline. */
|
2014-02-01 07:15:02 +08:00
|
|
|
firsterr = rcutorture_booster_init(i);
|
|
|
|
if (firsterr)
|
2010-09-03 07:16:14 +08:00
|
|
|
goto unwind;
|
|
|
|
}
|
|
|
|
}
|
2014-02-01 07:15:02 +08:00
|
|
|
firsterr = torture_shutdown_init(shutdown_secs, rcu_torture_cleanup);
|
|
|
|
if (firsterr)
|
2012-02-18 14:12:18 +08:00
|
|
|
goto unwind;
|
2014-02-01 07:15:02 +08:00
|
|
|
firsterr = torture_onoff_init(onoff_holdoff * HZ, onoff_interval * HZ);
|
|
|
|
if (firsterr)
|
2012-02-18 14:12:18 +08:00
|
|
|
goto unwind;
|
2014-02-01 07:15:02 +08:00
|
|
|
firsterr = rcu_torture_stall_init();
|
|
|
|
if (firsterr)
|
2012-02-18 14:12:18 +08:00
|
|
|
goto unwind;
|
2014-02-01 07:15:02 +08:00
|
|
|
firsterr = rcu_torture_barrier_init();
|
|
|
|
if (firsterr)
|
2012-02-21 09:51:45 +08:00
|
|
|
goto unwind;
|
2013-04-24 08:05:42 +08:00
|
|
|
if (object_debug)
|
|
|
|
rcu_test_debug_objects();
|
2011-04-04 12:33:51 +08:00
|
|
|
rcutorture_record_test_transition();
|
2014-01-31 05:38:09 +08:00
|
|
|
torture_init_end();
|
2005-10-31 07:03:12 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
unwind:
|
2014-01-31 05:38:09 +08:00
|
|
|
torture_init_end();
|
2005-10-31 07:03:12 +08:00
|
|
|
rcu_torture_cleanup();
|
|
|
|
return firsterr;
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(rcu_torture_init);
|
|
|
|
module_exit(rcu_torture_cleanup);
|