|
@@ -64,6 +64,7 @@ static int irqreader = 1; /* RCU readers from irq (timers). */
|
|
|
static int fqs_duration; /* Duration of bursts (us), 0 to disable. */
|
|
|
static int fqs_holdoff; /* Hold time within burst (us). */
|
|
|
static int fqs_stutter = 3; /* Wait time between bursts (s). */
|
|
|
+static int n_barrier_cbs; /* Number of callbacks to test RCU barriers. */
|
|
|
static int onoff_interval; /* Wait time between CPU hotplugs, 0=disable. */
|
|
|
static int onoff_holdoff; /* Seconds after boot before CPU hotplugs. */
|
|
|
static int shutdown_secs; /* Shutdown time (s). <=0 for no shutdown. */
|
|
@@ -96,6 +97,8 @@ module_param(fqs_holdoff, int, 0444);
|
|
|
MODULE_PARM_DESC(fqs_holdoff, "Holdoff time within fqs bursts (us)");
|
|
|
module_param(fqs_stutter, int, 0444);
|
|
|
MODULE_PARM_DESC(fqs_stutter, "Wait time between fqs bursts (s)");
|
|
|
+module_param(n_barrier_cbs, int, 0444);
|
|
|
+MODULE_PARM_DESC(n_barrier_cbs, "# of callbacks/kthreads for barrier testing");
|
|
|
module_param(onoff_interval, int, 0444);
|
|
|
MODULE_PARM_DESC(onoff_interval, "Time between CPU hotplugs (s), 0=disable");
|
|
|
module_param(onoff_holdoff, int, 0444);
|
|
@@ -139,6 +142,8 @@ static struct task_struct *shutdown_task;
|
|
|
static struct task_struct *onoff_task;
|
|
|
#endif /* #ifdef CONFIG_HOTPLUG_CPU */
|
|
|
static struct task_struct *stall_task;
|
|
|
+static struct task_struct **barrier_cbs_tasks;
|
|
|
+static struct task_struct *barrier_task;
|
|
|
|
|
|
#define RCU_TORTURE_PIPE_LEN 10
|
|
|
|
|
@@ -164,6 +169,7 @@ 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;
|
|
|
+static long n_rcu_torture_barrier_error;
|
|
|
static long n_rcu_torture_boost_ktrerror;
|
|
|
static long n_rcu_torture_boost_rterror;
|
|
|
static long n_rcu_torture_boost_failure;
|
|
@@ -173,6 +179,8 @@ static long n_offline_attempts;
|
|
|
static long n_offline_successes;
|
|
|
static long n_online_attempts;
|
|
|
static long n_online_successes;
|
|
|
+static long n_barrier_attempts;
|
|
|
+static long n_barrier_successes;
|
|
|
static struct list_head rcu_torture_removed;
|
|
|
static cpumask_var_t shuffle_tmp_mask;
|
|
|
|
|
@@ -197,6 +205,10 @@ static unsigned long shutdown_time; /* jiffies to system shutdown. */
|
|
|
static unsigned long boost_starttime; /* jiffies of next boost test start. */
|
|
|
DEFINE_MUTEX(boost_mutex); /* protect setting boost_starttime */
|
|
|
/* and boost task create/destroy. */
|
|
|
+static atomic_t barrier_cbs_count; /* Barrier callbacks registered. */
|
|
|
+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);
|
|
|
|
|
|
/* Mediate rmmod and system shutdown. Concurrent rmmod & shutdown illegal! */
|
|
|
|
|
@@ -327,6 +339,7 @@ struct rcu_torture_ops {
|
|
|
int (*completed)(void);
|
|
|
void (*deferred_free)(struct rcu_torture *p);
|
|
|
void (*sync)(void);
|
|
|
+ void (*call)(struct rcu_head *head, void (*func)(struct rcu_head *rcu));
|
|
|
void (*cb_barrier)(void);
|
|
|
void (*fqs)(void);
|
|
|
int (*stats)(char *page);
|
|
@@ -417,6 +430,7 @@ static struct rcu_torture_ops rcu_ops = {
|
|
|
.completed = rcu_torture_completed,
|
|
|
.deferred_free = rcu_torture_deferred_free,
|
|
|
.sync = synchronize_rcu,
|
|
|
+ .call = call_rcu,
|
|
|
.cb_barrier = rcu_barrier,
|
|
|
.fqs = rcu_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -460,6 +474,7 @@ static struct rcu_torture_ops rcu_sync_ops = {
|
|
|
.completed = rcu_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = synchronize_rcu,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.fqs = rcu_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -477,6 +492,7 @@ static struct rcu_torture_ops rcu_expedited_ops = {
|
|
|
.completed = rcu_no_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = synchronize_rcu_expedited,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.fqs = rcu_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -519,6 +535,7 @@ static struct rcu_torture_ops rcu_bh_ops = {
|
|
|
.completed = rcu_bh_torture_completed,
|
|
|
.deferred_free = rcu_bh_torture_deferred_free,
|
|
|
.sync = synchronize_rcu_bh,
|
|
|
+ .call = call_rcu_bh,
|
|
|
.cb_barrier = rcu_barrier_bh,
|
|
|
.fqs = rcu_bh_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -535,6 +552,7 @@ static struct rcu_torture_ops rcu_bh_sync_ops = {
|
|
|
.completed = rcu_bh_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = synchronize_rcu_bh,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.fqs = rcu_bh_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -551,6 +569,7 @@ static struct rcu_torture_ops rcu_bh_expedited_ops = {
|
|
|
.completed = rcu_bh_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = synchronize_rcu_bh_expedited,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.fqs = rcu_bh_force_quiescent_state,
|
|
|
.stats = NULL,
|
|
@@ -637,6 +656,7 @@ static struct rcu_torture_ops srcu_ops = {
|
|
|
.completed = srcu_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = srcu_torture_synchronize,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.stats = srcu_torture_stats,
|
|
|
.name = "srcu"
|
|
@@ -661,6 +681,7 @@ static struct rcu_torture_ops srcu_raw_ops = {
|
|
|
.completed = srcu_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = srcu_torture_synchronize,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.stats = srcu_torture_stats,
|
|
|
.name = "srcu_raw"
|
|
@@ -680,6 +701,7 @@ static struct rcu_torture_ops srcu_expedited_ops = {
|
|
|
.completed = srcu_torture_completed,
|
|
|
.deferred_free = rcu_sync_torture_deferred_free,
|
|
|
.sync = srcu_torture_synchronize_expedited,
|
|
|
+ .call = NULL,
|
|
|
.cb_barrier = NULL,
|
|
|
.stats = srcu_torture_stats,
|
|
|
.name = "srcu_expedited"
|
|
@@ -1129,7 +1151,8 @@ rcu_torture_printk(char *page)
|
|
|
"rtc: %p ver: %lu tfle: %d rta: %d rtaf: %d rtf: %d "
|
|
|
"rtmbe: %d rtbke: %ld rtbre: %ld "
|
|
|
"rtbf: %ld rtb: %ld nt: %ld "
|
|
|
- "onoff: %ld/%ld:%ld/%ld",
|
|
|
+ "onoff: %ld/%ld:%ld/%ld "
|
|
|
+ "barrier: %ld/%ld:%ld",
|
|
|
rcu_torture_current,
|
|
|
rcu_torture_current_version,
|
|
|
list_empty(&rcu_torture_freelist),
|
|
@@ -1145,14 +1168,17 @@ rcu_torture_printk(char *page)
|
|
|
n_online_successes,
|
|
|
n_online_attempts,
|
|
|
n_offline_successes,
|
|
|
- n_offline_attempts);
|
|
|
+ n_offline_attempts,
|
|
|
+ n_barrier_successes,
|
|
|
+ n_barrier_attempts,
|
|
|
+ n_rcu_torture_barrier_error);
|
|
|
+ cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG);
|
|
|
if (atomic_read(&n_rcu_torture_mberror) != 0 ||
|
|
|
+ n_rcu_torture_barrier_error != 0 ||
|
|
|
n_rcu_torture_boost_ktrerror != 0 ||
|
|
|
n_rcu_torture_boost_rterror != 0 ||
|
|
|
- n_rcu_torture_boost_failure != 0)
|
|
|
- cnt += sprintf(&page[cnt], " !!!");
|
|
|
- cnt += sprintf(&page[cnt], "\n%s%s ", torture_type, TORTURE_FLAG);
|
|
|
- if (i > 1) {
|
|
|
+ n_rcu_torture_boost_failure != 0 ||
|
|
|
+ i > 1) {
|
|
|
cnt += sprintf(&page[cnt], "!!! ");
|
|
|
atomic_inc(&n_rcu_torture_error);
|
|
|
WARN_ON_ONCE(1);
|
|
@@ -1560,6 +1586,151 @@ static void rcu_torture_stall_cleanup(void)
|
|
|
stall_task = NULL;
|
|
|
}
|
|
|
|
|
|
+/* Callback function for RCU barrier testing. */
|
|
|
+void rcu_torture_barrier_cbf(struct rcu_head *rcu)
|
|
|
+{
|
|
|
+ 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;
|
|
|
+ struct rcu_head rcu;
|
|
|
+
|
|
|
+ init_rcu_head_on_stack(&rcu);
|
|
|
+ VERBOSE_PRINTK_STRING("rcu_torture_barrier_cbs task started");
|
|
|
+ set_user_nice(current, 19);
|
|
|
+ do {
|
|
|
+ wait_event(barrier_cbs_wq[myid],
|
|
|
+ atomic_read(&barrier_cbs_count) == n_barrier_cbs ||
|
|
|
+ kthread_should_stop() ||
|
|
|
+ fullstop != FULLSTOP_DONTSTOP);
|
|
|
+ if (kthread_should_stop() || fullstop != FULLSTOP_DONTSTOP)
|
|
|
+ break;
|
|
|
+ cur_ops->call(&rcu, rcu_torture_barrier_cbf);
|
|
|
+ if (atomic_dec_and_test(&barrier_cbs_count))
|
|
|
+ wake_up(&barrier_wq);
|
|
|
+ } while (!kthread_should_stop() && fullstop == FULLSTOP_DONTSTOP);
|
|
|
+ VERBOSE_PRINTK_STRING("rcu_torture_barrier_cbs task stopping");
|
|
|
+ rcutorture_shutdown_absorb("rcu_torture_barrier_cbs");
|
|
|
+ while (!kthread_should_stop())
|
|
|
+ schedule_timeout_interruptible(1);
|
|
|
+ cur_ops->cb_barrier();
|
|
|
+ destroy_rcu_head_on_stack(&rcu);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* kthread function to drive and coordinate RCU barrier testing. */
|
|
|
+static int rcu_torture_barrier(void *arg)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ VERBOSE_PRINTK_STRING("rcu_torture_barrier task starting");
|
|
|
+ do {
|
|
|
+ atomic_set(&barrier_cbs_invoked, 0);
|
|
|
+ atomic_set(&barrier_cbs_count, n_barrier_cbs);
|
|
|
+ /* wake_up() path contains the required barriers. */
|
|
|
+ for (i = 0; i < n_barrier_cbs; i++)
|
|
|
+ wake_up(&barrier_cbs_wq[i]);
|
|
|
+ wait_event(barrier_wq,
|
|
|
+ atomic_read(&barrier_cbs_count) == 0 ||
|
|
|
+ kthread_should_stop() ||
|
|
|
+ fullstop != FULLSTOP_DONTSTOP);
|
|
|
+ if (kthread_should_stop() || fullstop != FULLSTOP_DONTSTOP)
|
|
|
+ break;
|
|
|
+ n_barrier_attempts++;
|
|
|
+ cur_ops->cb_barrier();
|
|
|
+ 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);
|
|
|
+ } while (!kthread_should_stop() && fullstop == FULLSTOP_DONTSTOP);
|
|
|
+ VERBOSE_PRINTK_STRING("rcu_torture_barrier task stopping");
|
|
|
+ rcutorture_shutdown_absorb("rcu_torture_barrier_cbs");
|
|
|
+ while (!kthread_should_stop())
|
|
|
+ schedule_timeout_interruptible(1);
|
|
|
+ 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) {
|
|
|
+ printk(KERN_ALERT "%s" TORTURE_FLAG
|
|
|
+ " Call or barrier ops missing for %s,\n",
|
|
|
+ torture_type, cur_ops->name);
|
|
|
+ printk(KERN_ALERT "%s" TORTURE_FLAG
|
|
|
+ " RCU barrier testing omitted from run.\n",
|
|
|
+ torture_type);
|
|
|
+ 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);
|
|
|
+ if (barrier_cbs_tasks == NULL || barrier_cbs_wq == 0)
|
|
|
+ return -ENOMEM;
|
|
|
+ for (i = 0; i < n_barrier_cbs; i++) {
|
|
|
+ init_waitqueue_head(&barrier_cbs_wq[i]);
|
|
|
+ barrier_cbs_tasks[i] = kthread_run(rcu_torture_barrier_cbs,
|
|
|
+ (void *)i,
|
|
|
+ "rcu_torture_barrier_cbs");
|
|
|
+ if (IS_ERR(barrier_cbs_tasks[i])) {
|
|
|
+ ret = PTR_ERR(barrier_cbs_tasks[i]);
|
|
|
+ VERBOSE_PRINTK_ERRSTRING("Failed to create rcu_torture_barrier_cbs");
|
|
|
+ barrier_cbs_tasks[i] = NULL;
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ barrier_task = kthread_run(rcu_torture_barrier, NULL,
|
|
|
+ "rcu_torture_barrier");
|
|
|
+ if (IS_ERR(barrier_task)) {
|
|
|
+ ret = PTR_ERR(barrier_task);
|
|
|
+ VERBOSE_PRINTK_ERRSTRING("Failed to create rcu_torture_barrier");
|
|
|
+ barrier_task = NULL;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* Clean up after RCU barrier testing. */
|
|
|
+static void rcu_torture_barrier_cleanup(void)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (barrier_task != NULL) {
|
|
|
+ VERBOSE_PRINTK_STRING("Stopping rcu_torture_barrier task");
|
|
|
+ kthread_stop(barrier_task);
|
|
|
+ barrier_task = NULL;
|
|
|
+ }
|
|
|
+ if (barrier_cbs_tasks != NULL) {
|
|
|
+ for (i = 0; i < n_barrier_cbs; i++) {
|
|
|
+ if (barrier_cbs_tasks[i] != NULL) {
|
|
|
+ VERBOSE_PRINTK_STRING("Stopping rcu_torture_barrier_cbs task");
|
|
|
+ kthread_stop(barrier_cbs_tasks[i]);
|
|
|
+ barrier_cbs_tasks[i] = NULL;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ kfree(barrier_cbs_tasks);
|
|
|
+ barrier_cbs_tasks = NULL;
|
|
|
+ }
|
|
|
+ if (barrier_cbs_wq != NULL) {
|
|
|
+ kfree(barrier_cbs_wq);
|
|
|
+ barrier_cbs_wq = NULL;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static int rcutorture_cpu_notify(struct notifier_block *self,
|
|
|
unsigned long action, void *hcpu)
|
|
|
{
|
|
@@ -1602,6 +1773,7 @@ rcu_torture_cleanup(void)
|
|
|
fullstop = FULLSTOP_RMMOD;
|
|
|
mutex_unlock(&fullstop_mutex);
|
|
|
unregister_reboot_notifier(&rcutorture_shutdown_nb);
|
|
|
+ rcu_torture_barrier_cleanup();
|
|
|
rcu_torture_stall_cleanup();
|
|
|
if (stutter_task) {
|
|
|
VERBOSE_PRINTK_STRING("Stopping rcu_torture_stutter task");
|
|
@@ -1681,7 +1853,7 @@ rcu_torture_cleanup(void)
|
|
|
|
|
|
if (cur_ops->cleanup)
|
|
|
cur_ops->cleanup();
|
|
|
- if (atomic_read(&n_rcu_torture_error))
|
|
|
+ if (atomic_read(&n_rcu_torture_error) || n_rcu_torture_barrier_error)
|
|
|
rcu_torture_print_module_parms(cur_ops, "End of test: FAILURE");
|
|
|
else if (n_online_successes != n_online_attempts ||
|
|
|
n_offline_successes != n_offline_attempts)
|
|
@@ -1697,6 +1869,7 @@ rcu_torture_init(void)
|
|
|
int i;
|
|
|
int cpu;
|
|
|
int firsterr = 0;
|
|
|
+ int retval;
|
|
|
static struct rcu_torture_ops *torture_ops[] =
|
|
|
{ &rcu_ops, &rcu_sync_ops, &rcu_expedited_ops,
|
|
|
&rcu_bh_ops, &rcu_bh_sync_ops, &rcu_bh_expedited_ops,
|
|
@@ -1754,6 +1927,7 @@ rcu_torture_init(void)
|
|
|
atomic_set(&n_rcu_torture_free, 0);
|
|
|
atomic_set(&n_rcu_torture_mberror, 0);
|
|
|
atomic_set(&n_rcu_torture_error, 0);
|
|
|
+ n_rcu_torture_barrier_error = 0;
|
|
|
n_rcu_torture_boost_ktrerror = 0;
|
|
|
n_rcu_torture_boost_rterror = 0;
|
|
|
n_rcu_torture_boost_failure = 0;
|
|
@@ -1877,7 +2051,6 @@ rcu_torture_init(void)
|
|
|
test_boost_duration = 2;
|
|
|
if ((test_boost == 1 && cur_ops->can_boost) ||
|
|
|
test_boost == 2) {
|
|
|
- int retval;
|
|
|
|
|
|
boost_starttime = jiffies + test_boost_interval * HZ;
|
|
|
register_cpu_notifier(&rcutorture_cpu_nb);
|
|
@@ -1913,6 +2086,11 @@ rcu_torture_init(void)
|
|
|
firsterr = i;
|
|
|
goto unwind;
|
|
|
}
|
|
|
+ retval = rcu_torture_barrier_init();
|
|
|
+ if (retval != 0) {
|
|
|
+ firsterr = retval;
|
|
|
+ goto unwind;
|
|
|
+ }
|
|
|
rcutorture_record_test_transition();
|
|
|
mutex_unlock(&fullstop_mutex);
|
|
|
return 0;
|