forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3190 lines
96 KiB
3190 lines
96 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* Read-Copy Update module-based torture test facility |
|
* |
|
* Copyright (C) IBM Corporation, 2005, 2006 |
|
* |
|
* Authors: Paul E. McKenney <[email protected]> |
|
* Josh Triplett <[email protected]> |
|
* |
|
* See also: Documentation/RCU/torture.rst |
|
*/ |
|
|
|
#define pr_fmt(fmt) fmt |
|
|
|
#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_wait.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/sched/signal.h> |
|
#include <uapi/linux/sched/types.h> |
|
#include <linux/atomic.h> |
|
#include <linux/bitops.h> |
|
#include <linux/completion.h> |
|
#include <linux/moduleparam.h> |
|
#include <linux/percpu.h> |
|
#include <linux/notifier.h> |
|
#include <linux/reboot.h> |
|
#include <linux/freezer.h> |
|
#include <linux/cpu.h> |
|
#include <linux/delay.h> |
|
#include <linux/stat.h> |
|
#include <linux/srcu.h> |
|
#include <linux/slab.h> |
|
#include <linux/trace_clock.h> |
|
#include <asm/byteorder.h> |
|
#include <linux/torture.h> |
|
#include <linux/vmalloc.h> |
|
#include <linux/sched/debug.h> |
|
#include <linux/sched/sysctl.h> |
|
#include <linux/oom.h> |
|
#include <linux/tick.h> |
|
#include <linux/rcupdate_trace.h> |
|
|
|
#include "rcu.h" |
|
|
|
MODULE_LICENSE("GPL"); |
|
MODULE_AUTHOR("Paul E. McKenney <[email protected]> and Josh Triplett <[email protected]>"); |
|
|
|
/* Bits for ->extendables field, extendables param, and related definitions. */ |
|
#define RCUTORTURE_RDR_SHIFT 8 /* Put SRCU index in upper bits. */ |
|
#define RCUTORTURE_RDR_MASK ((1 << RCUTORTURE_RDR_SHIFT) - 1) |
|
#define RCUTORTURE_RDR_BH 0x01 /* Extend readers by disabling bh. */ |
|
#define RCUTORTURE_RDR_IRQ 0x02 /* ... disabling interrupts. */ |
|
#define RCUTORTURE_RDR_PREEMPT 0x04 /* ... disabling preemption. */ |
|
#define RCUTORTURE_RDR_RBH 0x08 /* ... rcu_read_lock_bh(). */ |
|
#define RCUTORTURE_RDR_SCHED 0x10 /* ... rcu_read_lock_sched(). */ |
|
#define RCUTORTURE_RDR_RCU 0x20 /* ... entering another RCU reader. */ |
|
#define RCUTORTURE_RDR_NBITS 6 /* Number of bits defined above. */ |
|
#define RCUTORTURE_MAX_EXTEND \ |
|
(RCUTORTURE_RDR_BH | RCUTORTURE_RDR_IRQ | RCUTORTURE_RDR_PREEMPT | \ |
|
RCUTORTURE_RDR_RBH | RCUTORTURE_RDR_SCHED) |
|
#define RCUTORTURE_RDR_MAX_LOOPS 0x7 /* Maximum reader extensions. */ |
|
/* Must be power of two minus one. */ |
|
#define RCUTORTURE_RDR_MAX_SEGS (RCUTORTURE_RDR_MAX_LOOPS + 3) |
|
|
|
torture_param(int, extendables, RCUTORTURE_MAX_EXTEND, |
|
"Extend readers by disabling bh (1), irqs (2), or preempt (4)"); |
|
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)"); |
|
torture_param(bool, fwd_progress, 1, "Test grace-period forward progress"); |
|
torture_param(int, fwd_progress_div, 4, "Fraction of CPU stall to wait"); |
|
torture_param(int, fwd_progress_holdoff, 60, |
|
"Time between forward-progress tests (s)"); |
|
torture_param(bool, fwd_progress_need_resched, 1, |
|
"Hide cond_resched() behind need_resched()"); |
|
torture_param(bool, gp_cond, false, "Use conditional/async GP wait primitives"); |
|
torture_param(bool, gp_exp, false, "Use expedited GP wait primitives"); |
|
torture_param(bool, gp_normal, false, |
|
"Use normal (non-expedited) GP wait primitives"); |
|
torture_param(bool, gp_poll, false, "Use polling GP wait primitives"); |
|
torture_param(bool, gp_sync, false, "Use synchronous GP wait primitives"); |
|
torture_param(int, irqreader, 1, "Allow RCU readers from irq handlers"); |
|
torture_param(int, leakpointer, 0, "Leak pointer dereferences from readers"); |
|
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 (jiffies), 0=disable"); |
|
torture_param(int, nocbs_nthreads, 0, "Number of NOCB toggle threads, 0 to disable"); |
|
torture_param(int, nocbs_toggle, 1000, "Time between toggling nocb state (ms)"); |
|
torture_param(int, read_exit_delay, 13, |
|
"Delay between read-then-exit episodes (s)"); |
|
torture_param(int, read_exit_burst, 16, |
|
"# of read-then-exit bursts per episode, zero to 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, stall_cpu_irqsoff, 0, "Disable interrupts while stalling."); |
|
torture_param(int, stall_cpu_block, 0, "Sleep while stalling."); |
|
torture_param(int, stall_gp_kthread, 0, |
|
"Grace-period kthread stall duration (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"); |
|
torture_param(int, verbose, 1, |
|
"Enable verbose debugging printk()s"); |
|
|
|
static char *torture_type = "rcu"; |
|
module_param(torture_type, charp, 0444); |
|
MODULE_PARM_DESC(torture_type, "Type of RCU to torture (rcu, srcu, ...)"); |
|
|
|
static int nrealnocbers; |
|
static int nrealreaders; |
|
static struct task_struct *writer_task; |
|
static struct task_struct **fakewriter_tasks; |
|
static struct task_struct **reader_tasks; |
|
static struct task_struct **nocb_tasks; |
|
static struct task_struct *stats_task; |
|
static struct task_struct *fqs_task; |
|
static struct task_struct *boost_tasks[NR_CPUS]; |
|
static struct task_struct *stall_task; |
|
static struct task_struct *fwd_prog_task; |
|
static struct task_struct **barrier_cbs_tasks; |
|
static struct task_struct *barrier_task; |
|
static struct task_struct *read_exit_task; |
|
|
|
#define RCU_TORTURE_PIPE_LEN 10 |
|
|
|
// Mailbox-like structure to check RCU global memory ordering. |
|
struct rcu_torture_reader_check { |
|
unsigned long rtc_myloops; |
|
int rtc_chkrdr; |
|
unsigned long rtc_chkloops; |
|
int rtc_ready; |
|
struct rcu_torture_reader_check *rtc_assigner; |
|
} ____cacheline_internodealigned_in_smp; |
|
|
|
// Update-side data structure used to check RCU readers. |
|
struct rcu_torture { |
|
struct rcu_head rtort_rcu; |
|
int rtort_pipe_count; |
|
struct list_head rtort_free; |
|
int rtort_mbtest; |
|
struct rcu_torture_reader_check *rtort_chkp; |
|
}; |
|
|
|
static LIST_HEAD(rcu_torture_freelist); |
|
static struct rcu_torture __rcu *rcu_torture_current; |
|
static unsigned long rcu_torture_current_version; |
|
static struct rcu_torture rcu_tortures[10 * RCU_TORTURE_PIPE_LEN]; |
|
static DEFINE_SPINLOCK(rcu_torture_lock); |
|
static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1], rcu_torture_count); |
|
static DEFINE_PER_CPU(long [RCU_TORTURE_PIPE_LEN + 1], rcu_torture_batch); |
|
static atomic_t rcu_torture_wcount[RCU_TORTURE_PIPE_LEN + 1]; |
|
static struct rcu_torture_reader_check *rcu_torture_reader_mbchk; |
|
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_mbchk_fail; |
|
static atomic_t n_rcu_torture_mbchk_tries; |
|
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; |
|
static long n_rcu_torture_boosts; |
|
static atomic_long_t n_rcu_torture_timers; |
|
static long n_barrier_attempts; |
|
static long n_barrier_successes; /* did rcu_barrier test succeed? */ |
|
static unsigned long n_read_exits; |
|
static struct list_head rcu_torture_removed; |
|
static unsigned long shutdown_jiffies; |
|
static unsigned long start_gp_seq; |
|
static atomic_long_t n_nocb_offload; |
|
static atomic_long_t n_nocb_deoffload; |
|
|
|
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 |
|
#define RTWS_COND_GET 5 |
|
#define RTWS_COND_SYNC 6 |
|
#define RTWS_POLL_GET 7 |
|
#define RTWS_POLL_WAIT 8 |
|
#define RTWS_SYNC 9 |
|
#define RTWS_STUTTER 10 |
|
#define RTWS_STOPPING 11 |
|
static const char * const rcu_torture_writer_state_names[] = { |
|
"RTWS_FIXED_DELAY", |
|
"RTWS_DELAY", |
|
"RTWS_REPLACE", |
|
"RTWS_DEF_FREE", |
|
"RTWS_EXP_SYNC", |
|
"RTWS_COND_GET", |
|
"RTWS_COND_SYNC", |
|
"RTWS_POLL_GET", |
|
"RTWS_POLL_WAIT", |
|
"RTWS_SYNC", |
|
"RTWS_STUTTER", |
|
"RTWS_STOPPING", |
|
}; |
|
|
|
/* Record reader segment types and duration for first failing read. */ |
|
struct rt_read_seg { |
|
int rt_readstate; |
|
unsigned long rt_delay_jiffies; |
|
unsigned long rt_delay_ms; |
|
unsigned long rt_delay_us; |
|
bool rt_preempted; |
|
}; |
|
static int err_segs_recorded; |
|
static struct rt_read_seg err_segs[RCUTORTURE_RDR_MAX_SEGS]; |
|
static int rt_read_nsegs; |
|
|
|
static const char *rcu_torture_writer_state_getname(void) |
|
{ |
|
unsigned int i = READ_ONCE(rcu_torture_writer_state); |
|
|
|
if (i >= ARRAY_SIZE(rcu_torture_writer_state_names)) |
|
return "???"; |
|
return rcu_torture_writer_state_names[i]; |
|
} |
|
|
|
#ifdef CONFIG_RCU_TRACE |
|
static u64 notrace rcu_trace_clock_local(void) |
|
{ |
|
u64 ts = trace_clock_local(); |
|
|
|
(void)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 */ |
|
|
|
/* |
|
* Stop aggressive CPU-hog tests a bit before the end of the test in order |
|
* to avoid interfering with test shutdown. |
|
*/ |
|
static bool shutdown_time_arrived(void) |
|
{ |
|
return shutdown_secs && time_after(jiffies, shutdown_jiffies - 30 * HZ); |
|
} |
|
|
|
static unsigned long boost_starttime; /* jiffies of next boost test start. */ |
|
static DEFINE_MUTEX(boost_mutex); /* protect setting boost_starttime */ |
|
/* and boost task create/destroy. */ |
|
static atomic_t barrier_cbs_count; /* Barrier callbacks registered. */ |
|
static bool barrier_phase; /* Test phase. */ |
|
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); |
|
|
|
static bool rcu_fwd_cb_nodelay; /* Short rcu_torture_delay() delays. */ |
|
|
|
/* |
|
* Allocate an element from the rcu_tortures pool. |
|
*/ |
|
static struct rcu_torture * |
|
rcu_torture_alloc(void) |
|
{ |
|
struct list_head *p; |
|
|
|
spin_lock_bh(&rcu_torture_lock); |
|
if (list_empty(&rcu_torture_freelist)) { |
|
atomic_inc(&n_rcu_torture_alloc_fail); |
|
spin_unlock_bh(&rcu_torture_lock); |
|
return NULL; |
|
} |
|
atomic_inc(&n_rcu_torture_alloc); |
|
p = rcu_torture_freelist.next; |
|
list_del_init(p); |
|
spin_unlock_bh(&rcu_torture_lock); |
|
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); |
|
spin_lock_bh(&rcu_torture_lock); |
|
list_add_tail(&p->rtort_free, &rcu_torture_freelist); |
|
spin_unlock_bh(&rcu_torture_lock); |
|
} |
|
|
|
/* |
|
* Operations vector for selecting different types of tests. |
|
*/ |
|
|
|
struct rcu_torture_ops { |
|
int ttype; |
|
void (*init)(void); |
|
void (*cleanup)(void); |
|
int (*readlock)(void); |
|
void (*read_delay)(struct torture_random_state *rrsp, |
|
struct rt_read_seg *rtrsp); |
|
void (*readunlock)(int idx); |
|
int (*readlock_held)(void); |
|
unsigned long (*get_gp_seq)(void); |
|
unsigned long (*gp_diff)(unsigned long new, unsigned long old); |
|
void (*deferred_free)(struct rcu_torture *p); |
|
void (*sync)(void); |
|
void (*exp_sync)(void); |
|
unsigned long (*get_gp_state)(void); |
|
unsigned long (*start_gp_poll)(void); |
|
bool (*poll_gp_state)(unsigned long oldstate); |
|
void (*cond_sync)(unsigned long oldstate); |
|
call_rcu_func_t call; |
|
void (*cb_barrier)(void); |
|
void (*fqs)(void); |
|
void (*stats)(void); |
|
void (*gp_kthread_dbg)(void); |
|
bool (*check_boost_failed)(unsigned long gp_state, int *cpup); |
|
int (*stall_dur)(void); |
|
int irq_capable; |
|
int can_boost; |
|
int extendables; |
|
int slow_gps; |
|
const char *name; |
|
}; |
|
|
|
static struct rcu_torture_ops *cur_ops; |
|
|
|
/* |
|
* Definitions for rcu torture testing. |
|
*/ |
|
|
|
static int torture_readlock_not_held(void) |
|
{ |
|
return rcu_read_lock_bh_held() || rcu_read_lock_sched_held(); |
|
} |
|
|
|
static int rcu_torture_read_lock(void) __acquires(RCU) |
|
{ |
|
rcu_read_lock(); |
|
return 0; |
|
} |
|
|
|
static void |
|
rcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp) |
|
{ |
|
unsigned long started; |
|
unsigned long completed; |
|
const unsigned long shortdelay_us = 200; |
|
unsigned long longdelay_ms = 300; |
|
unsigned long long ts; |
|
|
|
/* 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. */ |
|
|
|
if (!READ_ONCE(rcu_fwd_cb_nodelay) && |
|
!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) { |
|
started = cur_ops->get_gp_seq(); |
|
ts = rcu_trace_clock_local(); |
|
if (preempt_count() & (SOFTIRQ_MASK | HARDIRQ_MASK)) |
|
longdelay_ms = 5; /* Avoid triggering BH limits. */ |
|
mdelay(longdelay_ms); |
|
rtrsp->rt_delay_ms = longdelay_ms; |
|
completed = cur_ops->get_gp_seq(); |
|
do_trace_rcu_torture_read(cur_ops->name, NULL, ts, |
|
started, completed); |
|
} |
|
if (!(torture_random(rrsp) % (nrealreaders * 2 * shortdelay_us))) { |
|
udelay(shortdelay_us); |
|
rtrsp->rt_delay_us = shortdelay_us; |
|
} |
|
if (!preempt_count() && |
|
!(torture_random(rrsp) % (nrealreaders * 500))) { |
|
torture_preempt_schedule(); /* QS only if preemptible. */ |
|
rtrsp->rt_preempted = true; |
|
} |
|
} |
|
|
|
static void rcu_torture_read_unlock(int idx) __releases(RCU) |
|
{ |
|
rcu_read_unlock(); |
|
} |
|
|
|
/* |
|
* 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; |
|
struct rcu_torture_reader_check *rtrcp = READ_ONCE(rp->rtort_chkp); |
|
|
|
if (rtrcp) { |
|
WRITE_ONCE(rp->rtort_chkp, NULL); |
|
smp_store_release(&rtrcp->rtc_ready, 1); // Pair with smp_load_acquire(). |
|
} |
|
i = READ_ONCE(rp->rtort_pipe_count); |
|
if (i > RCU_TORTURE_PIPE_LEN) |
|
i = RCU_TORTURE_PIPE_LEN; |
|
atomic_inc(&rcu_torture_wcount[i]); |
|
WRITE_ONCE(rp->rtort_pipe_count, i + 1); |
|
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); |
|
} |
|
} |
|
} |
|
|
|
static void |
|
rcu_torture_cb(struct rcu_head *p) |
|
{ |
|
struct rcu_torture *rp = container_of(p, struct rcu_torture, rtort_rcu); |
|
|
|
if (torture_must_stop_irq()) { |
|
/* Test is ending, just drop callbacks on the floor. */ |
|
/* The next initialization will pick up the pieces. */ |
|
return; |
|
} |
|
if (rcu_torture_pipe_update_one(rp)) |
|
rcu_torture_free(rp); |
|
else |
|
cur_ops->deferred_free(rp); |
|
} |
|
|
|
static unsigned long rcu_no_completed(void) |
|
{ |
|
return 0; |
|
} |
|
|
|
static void rcu_torture_deferred_free(struct rcu_torture *p) |
|
{ |
|
call_rcu(&p->rtort_rcu, rcu_torture_cb); |
|
} |
|
|
|
static void rcu_sync_torture_init(void) |
|
{ |
|
INIT_LIST_HEAD(&rcu_torture_removed); |
|
} |
|
|
|
static struct rcu_torture_ops rcu_ops = { |
|
.ttype = RCU_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = rcu_torture_read_lock, |
|
.read_delay = rcu_read_delay, |
|
.readunlock = rcu_torture_read_unlock, |
|
.readlock_held = torture_readlock_not_held, |
|
.get_gp_seq = rcu_get_gp_seq, |
|
.gp_diff = rcu_seq_diff, |
|
.deferred_free = rcu_torture_deferred_free, |
|
.sync = synchronize_rcu, |
|
.exp_sync = synchronize_rcu_expedited, |
|
.get_gp_state = get_state_synchronize_rcu, |
|
.start_gp_poll = start_poll_synchronize_rcu, |
|
.poll_gp_state = poll_state_synchronize_rcu, |
|
.cond_sync = cond_synchronize_rcu, |
|
.call = call_rcu, |
|
.cb_barrier = rcu_barrier, |
|
.fqs = rcu_force_quiescent_state, |
|
.stats = NULL, |
|
.gp_kthread_dbg = show_rcu_gp_kthreads, |
|
.check_boost_failed = rcu_check_boost_fail, |
|
.stall_dur = rcu_jiffies_till_stall_check, |
|
.irq_capable = 1, |
|
.can_boost = IS_ENABLED(CONFIG_RCU_BOOST), |
|
.extendables = RCUTORTURE_MAX_EXTEND, |
|
.name = "rcu" |
|
}; |
|
|
|
/* |
|
* 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, rcu_callback_t func) |
|
{ |
|
/* This is a deliberate bug for testing purposes only! */ |
|
func(head); |
|
} |
|
|
|
static struct rcu_torture_ops rcu_busted_ops = { |
|
.ttype = INVALID_RCU_FLAVOR, |
|
.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, |
|
.readlock_held = torture_readlock_not_held, |
|
.get_gp_seq = 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 = "busted" |
|
}; |
|
|
|
/* |
|
* Definitions for srcu torture testing. |
|
*/ |
|
|
|
DEFINE_STATIC_SRCU(srcu_ctl); |
|
static struct srcu_struct srcu_ctld; |
|
static struct srcu_struct *srcu_ctlp = &srcu_ctl; |
|
|
|
static int srcu_torture_read_lock(void) __acquires(srcu_ctlp) |
|
{ |
|
return srcu_read_lock(srcu_ctlp); |
|
} |
|
|
|
static void |
|
srcu_read_delay(struct torture_random_state *rrsp, struct rt_read_seg *rtrsp) |
|
{ |
|
long delay; |
|
const long uspertick = 1000000 / HZ; |
|
const long longdelay = 10; |
|
|
|
/* We want there to be long-running readers, but not all the time. */ |
|
|
|
delay = torture_random(rrsp) % |
|
(nrealreaders * 2 * longdelay * uspertick); |
|
if (!delay && in_task()) { |
|
schedule_timeout_interruptible(longdelay); |
|
rtrsp->rt_delay_jiffies = longdelay; |
|
} else { |
|
rcu_read_delay(rrsp, rtrsp); |
|
} |
|
} |
|
|
|
static void srcu_torture_read_unlock(int idx) __releases(srcu_ctlp) |
|
{ |
|
srcu_read_unlock(srcu_ctlp, idx); |
|
} |
|
|
|
static int torture_srcu_read_lock_held(void) |
|
{ |
|
return srcu_read_lock_held(srcu_ctlp); |
|
} |
|
|
|
static unsigned long srcu_torture_completed(void) |
|
{ |
|
return srcu_batches_completed(srcu_ctlp); |
|
} |
|
|
|
static void srcu_torture_deferred_free(struct rcu_torture *rp) |
|
{ |
|
call_srcu(srcu_ctlp, &rp->rtort_rcu, rcu_torture_cb); |
|
} |
|
|
|
static void srcu_torture_synchronize(void) |
|
{ |
|
synchronize_srcu(srcu_ctlp); |
|
} |
|
|
|
static unsigned long srcu_torture_get_gp_state(void) |
|
{ |
|
return get_state_synchronize_srcu(srcu_ctlp); |
|
} |
|
|
|
static unsigned long srcu_torture_start_gp_poll(void) |
|
{ |
|
return start_poll_synchronize_srcu(srcu_ctlp); |
|
} |
|
|
|
static bool srcu_torture_poll_gp_state(unsigned long oldstate) |
|
{ |
|
return poll_state_synchronize_srcu(srcu_ctlp, oldstate); |
|
} |
|
|
|
static void srcu_torture_call(struct rcu_head *head, |
|
rcu_callback_t func) |
|
{ |
|
call_srcu(srcu_ctlp, head, func); |
|
} |
|
|
|
static void srcu_torture_barrier(void) |
|
{ |
|
srcu_barrier(srcu_ctlp); |
|
} |
|
|
|
static void srcu_torture_stats(void) |
|
{ |
|
srcu_torture_stats_print(srcu_ctlp, torture_type, TORTURE_FLAG); |
|
} |
|
|
|
static void srcu_torture_synchronize_expedited(void) |
|
{ |
|
synchronize_srcu_expedited(srcu_ctlp); |
|
} |
|
|
|
static struct rcu_torture_ops srcu_ops = { |
|
.ttype = SRCU_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = srcu_torture_read_lock, |
|
.read_delay = srcu_read_delay, |
|
.readunlock = srcu_torture_read_unlock, |
|
.readlock_held = torture_srcu_read_lock_held, |
|
.get_gp_seq = srcu_torture_completed, |
|
.deferred_free = srcu_torture_deferred_free, |
|
.sync = srcu_torture_synchronize, |
|
.exp_sync = srcu_torture_synchronize_expedited, |
|
.get_gp_state = srcu_torture_get_gp_state, |
|
.start_gp_poll = srcu_torture_start_gp_poll, |
|
.poll_gp_state = srcu_torture_poll_gp_state, |
|
.call = srcu_torture_call, |
|
.cb_barrier = srcu_torture_barrier, |
|
.stats = srcu_torture_stats, |
|
.irq_capable = 1, |
|
.name = "srcu" |
|
}; |
|
|
|
static void srcu_torture_init(void) |
|
{ |
|
rcu_sync_torture_init(); |
|
WARN_ON(init_srcu_struct(&srcu_ctld)); |
|
srcu_ctlp = &srcu_ctld; |
|
} |
|
|
|
static void srcu_torture_cleanup(void) |
|
{ |
|
cleanup_srcu_struct(&srcu_ctld); |
|
srcu_ctlp = &srcu_ctl; /* In case of a later rcutorture run. */ |
|
} |
|
|
|
/* As above, but dynamically allocated. */ |
|
static struct rcu_torture_ops srcud_ops = { |
|
.ttype = SRCU_FLAVOR, |
|
.init = srcu_torture_init, |
|
.cleanup = srcu_torture_cleanup, |
|
.readlock = srcu_torture_read_lock, |
|
.read_delay = srcu_read_delay, |
|
.readunlock = srcu_torture_read_unlock, |
|
.readlock_held = torture_srcu_read_lock_held, |
|
.get_gp_seq = srcu_torture_completed, |
|
.deferred_free = srcu_torture_deferred_free, |
|
.sync = srcu_torture_synchronize, |
|
.exp_sync = srcu_torture_synchronize_expedited, |
|
.call = srcu_torture_call, |
|
.cb_barrier = srcu_torture_barrier, |
|
.stats = srcu_torture_stats, |
|
.irq_capable = 1, |
|
.name = "srcud" |
|
}; |
|
|
|
/* As above, but broken due to inappropriate reader extension. */ |
|
static struct rcu_torture_ops busted_srcud_ops = { |
|
.ttype = SRCU_FLAVOR, |
|
.init = srcu_torture_init, |
|
.cleanup = srcu_torture_cleanup, |
|
.readlock = srcu_torture_read_lock, |
|
.read_delay = rcu_read_delay, |
|
.readunlock = srcu_torture_read_unlock, |
|
.readlock_held = torture_srcu_read_lock_held, |
|
.get_gp_seq = srcu_torture_completed, |
|
.deferred_free = srcu_torture_deferred_free, |
|
.sync = srcu_torture_synchronize, |
|
.exp_sync = srcu_torture_synchronize_expedited, |
|
.call = srcu_torture_call, |
|
.cb_barrier = srcu_torture_barrier, |
|
.stats = srcu_torture_stats, |
|
.irq_capable = 1, |
|
.extendables = RCUTORTURE_MAX_EXTEND, |
|
.name = "busted_srcud" |
|
}; |
|
|
|
/* |
|
* Definitions for RCU-tasks torture testing. |
|
*/ |
|
|
|
static int tasks_torture_read_lock(void) |
|
{ |
|
return 0; |
|
} |
|
|
|
static void tasks_torture_read_unlock(int idx) |
|
{ |
|
} |
|
|
|
static void rcu_tasks_torture_deferred_free(struct rcu_torture *p) |
|
{ |
|
call_rcu_tasks(&p->rtort_rcu, rcu_torture_cb); |
|
} |
|
|
|
static void synchronize_rcu_mult_test(void) |
|
{ |
|
synchronize_rcu_mult(call_rcu_tasks, call_rcu); |
|
} |
|
|
|
static struct rcu_torture_ops tasks_ops = { |
|
.ttype = RCU_TASKS_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = tasks_torture_read_lock, |
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */ |
|
.readunlock = tasks_torture_read_unlock, |
|
.get_gp_seq = rcu_no_completed, |
|
.deferred_free = rcu_tasks_torture_deferred_free, |
|
.sync = synchronize_rcu_tasks, |
|
.exp_sync = synchronize_rcu_mult_test, |
|
.call = call_rcu_tasks, |
|
.cb_barrier = rcu_barrier_tasks, |
|
.gp_kthread_dbg = show_rcu_tasks_classic_gp_kthread, |
|
.fqs = NULL, |
|
.stats = NULL, |
|
.irq_capable = 1, |
|
.slow_gps = 1, |
|
.name = "tasks" |
|
}; |
|
|
|
/* |
|
* Definitions for trivial CONFIG_PREEMPT=n-only torture testing. |
|
* This implementation does not necessarily work well with CPU hotplug. |
|
*/ |
|
|
|
static void synchronize_rcu_trivial(void) |
|
{ |
|
int cpu; |
|
|
|
for_each_online_cpu(cpu) { |
|
rcutorture_sched_setaffinity(current->pid, cpumask_of(cpu)); |
|
WARN_ON_ONCE(raw_smp_processor_id() != cpu); |
|
} |
|
} |
|
|
|
static int rcu_torture_read_lock_trivial(void) __acquires(RCU) |
|
{ |
|
preempt_disable(); |
|
return 0; |
|
} |
|
|
|
static void rcu_torture_read_unlock_trivial(int idx) __releases(RCU) |
|
{ |
|
preempt_enable(); |
|
} |
|
|
|
static struct rcu_torture_ops trivial_ops = { |
|
.ttype = RCU_TRIVIAL_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = rcu_torture_read_lock_trivial, |
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */ |
|
.readunlock = rcu_torture_read_unlock_trivial, |
|
.readlock_held = torture_readlock_not_held, |
|
.get_gp_seq = rcu_no_completed, |
|
.sync = synchronize_rcu_trivial, |
|
.exp_sync = synchronize_rcu_trivial, |
|
.fqs = NULL, |
|
.stats = NULL, |
|
.irq_capable = 1, |
|
.name = "trivial" |
|
}; |
|
|
|
/* |
|
* Definitions for rude RCU-tasks torture testing. |
|
*/ |
|
|
|
static void rcu_tasks_rude_torture_deferred_free(struct rcu_torture *p) |
|
{ |
|
call_rcu_tasks_rude(&p->rtort_rcu, rcu_torture_cb); |
|
} |
|
|
|
static struct rcu_torture_ops tasks_rude_ops = { |
|
.ttype = RCU_TASKS_RUDE_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = rcu_torture_read_lock_trivial, |
|
.read_delay = rcu_read_delay, /* just reuse rcu's version. */ |
|
.readunlock = rcu_torture_read_unlock_trivial, |
|
.get_gp_seq = rcu_no_completed, |
|
.deferred_free = rcu_tasks_rude_torture_deferred_free, |
|
.sync = synchronize_rcu_tasks_rude, |
|
.exp_sync = synchronize_rcu_tasks_rude, |
|
.call = call_rcu_tasks_rude, |
|
.cb_barrier = rcu_barrier_tasks_rude, |
|
.gp_kthread_dbg = show_rcu_tasks_rude_gp_kthread, |
|
.fqs = NULL, |
|
.stats = NULL, |
|
.irq_capable = 1, |
|
.name = "tasks-rude" |
|
}; |
|
|
|
/* |
|
* Definitions for tracing RCU-tasks torture testing. |
|
*/ |
|
|
|
static int tasks_tracing_torture_read_lock(void) |
|
{ |
|
rcu_read_lock_trace(); |
|
return 0; |
|
} |
|
|
|
static void tasks_tracing_torture_read_unlock(int idx) |
|
{ |
|
rcu_read_unlock_trace(); |
|
} |
|
|
|
static void rcu_tasks_tracing_torture_deferred_free(struct rcu_torture *p) |
|
{ |
|
call_rcu_tasks_trace(&p->rtort_rcu, rcu_torture_cb); |
|
} |
|
|
|
static struct rcu_torture_ops tasks_tracing_ops = { |
|
.ttype = RCU_TASKS_TRACING_FLAVOR, |
|
.init = rcu_sync_torture_init, |
|
.readlock = tasks_tracing_torture_read_lock, |
|
.read_delay = srcu_read_delay, /* just reuse srcu's version. */ |
|
.readunlock = tasks_tracing_torture_read_unlock, |
|
.readlock_held = rcu_read_lock_trace_held, |
|
.get_gp_seq = rcu_no_completed, |
|
.deferred_free = rcu_tasks_tracing_torture_deferred_free, |
|
.sync = synchronize_rcu_tasks_trace, |
|
.exp_sync = synchronize_rcu_tasks_trace, |
|
.call = call_rcu_tasks_trace, |
|
.cb_barrier = rcu_barrier_tasks_trace, |
|
.gp_kthread_dbg = show_rcu_tasks_trace_gp_kthread, |
|
.fqs = NULL, |
|
.stats = NULL, |
|
.irq_capable = 1, |
|
.slow_gps = 1, |
|
.name = "tasks-tracing" |
|
}; |
|
|
|
static unsigned long rcutorture_seq_diff(unsigned long new, unsigned long old) |
|
{ |
|
if (!cur_ops->gp_diff) |
|
return new - old; |
|
return cur_ops->gp_diff(new, old); |
|
} |
|
|
|
/* |
|
* RCU torture priority-boost testing. Runs one real-time thread per |
|
* CPU for moderate bursts, repeatedly starting grace periods and waiting |
|
* for them to complete. If a given grace period takes too long, we assume |
|
* that priority inversion has occurred. |
|
*/ |
|
|
|
static int old_rt_runtime = -1; |
|
|
|
static void rcu_torture_disable_rt_throttle(void) |
|
{ |
|
/* |
|
* Disable RT throttling so that rcutorture's boost threads don't get |
|
* throttled. Only possible if rcutorture is built-in otherwise the |
|
* user should manually do this by setting the sched_rt_period_us and |
|
* sched_rt_runtime sysctls. |
|
*/ |
|
if (!IS_BUILTIN(CONFIG_RCU_TORTURE_TEST) || old_rt_runtime != -1) |
|
return; |
|
|
|
old_rt_runtime = sysctl_sched_rt_runtime; |
|
sysctl_sched_rt_runtime = -1; |
|
} |
|
|
|
static void rcu_torture_enable_rt_throttle(void) |
|
{ |
|
if (!IS_BUILTIN(CONFIG_RCU_TORTURE_TEST) || old_rt_runtime == -1) |
|
return; |
|
|
|
sysctl_sched_rt_runtime = old_rt_runtime; |
|
old_rt_runtime = -1; |
|
} |
|
|
|
static bool rcu_torture_boost_failed(unsigned long gp_state, unsigned long *start) |
|
{ |
|
int cpu; |
|
static int dbg_done; |
|
unsigned long end = jiffies; |
|
bool gp_done; |
|
unsigned long j; |
|
static unsigned long last_persist; |
|
unsigned long lp; |
|
unsigned long mininterval = test_boost_duration * HZ - HZ / 2; |
|
|
|
if (end - *start > mininterval) { |
|
// Recheck after checking time to avoid false positives. |
|
smp_mb(); // Time check before grace-period check. |
|
if (cur_ops->poll_gp_state(gp_state)) |
|
return false; // passed, though perhaps just barely |
|
if (cur_ops->check_boost_failed && !cur_ops->check_boost_failed(gp_state, &cpu)) { |
|
// At most one persisted message per boost test. |
|
j = jiffies; |
|
lp = READ_ONCE(last_persist); |
|
if (time_after(j, lp + mininterval) && cmpxchg(&last_persist, lp, j) == lp) |
|
pr_info("Boost inversion persisted: No QS from CPU %d\n", cpu); |
|
return false; // passed on a technicality |
|
} |
|
VERBOSE_TOROUT_STRING("rcu_torture_boost boosting failed"); |
|
n_rcu_torture_boost_failure++; |
|
if (!xchg(&dbg_done, 1) && cur_ops->gp_kthread_dbg) { |
|
pr_info("Boost inversion thread ->rt_priority %u gp_state %lu jiffies %lu\n", |
|
current->rt_priority, gp_state, end - *start); |
|
cur_ops->gp_kthread_dbg(); |
|
// Recheck after print to flag grace period ending during splat. |
|
gp_done = cur_ops->poll_gp_state(gp_state); |
|
pr_info("Boost inversion: GP %lu %s.\n", gp_state, |
|
gp_done ? "ended already" : "still pending"); |
|
|
|
} |
|
|
|
return true; // failed |
|
} else if (cur_ops->check_boost_failed && !cur_ops->check_boost_failed(gp_state, NULL)) { |
|
*start = jiffies; |
|
} |
|
|
|
return false; // passed |
|
} |
|
|
|
static int rcu_torture_boost(void *arg) |
|
{ |
|
unsigned long endtime; |
|
unsigned long gp_state; |
|
unsigned long gp_state_time; |
|
unsigned long oldstarttime; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_boost started"); |
|
|
|
/* Set real-time priority. */ |
|
sched_set_fifo_low(current); |
|
|
|
/* Each pass through the following loop does one boost-test cycle. */ |
|
do { |
|
bool failed = false; // Test failed already in this test interval |
|
bool gp_initiated = false; |
|
|
|
if (kthread_should_stop()) |
|
goto checkwait; |
|
|
|
/* Wait for the next test interval. */ |
|
oldstarttime = boost_starttime; |
|
while (time_before(jiffies, oldstarttime)) { |
|
schedule_timeout_interruptible(oldstarttime - jiffies); |
|
if (stutter_wait("rcu_torture_boost")) |
|
sched_set_fifo_low(current); |
|
if (torture_must_stop()) |
|
goto checkwait; |
|
} |
|
|
|
// Do one boost-test interval. |
|
endtime = oldstarttime + test_boost_duration * HZ; |
|
while (time_before(jiffies, endtime)) { |
|
// Has current GP gone too long? |
|
if (gp_initiated && !failed && !cur_ops->poll_gp_state(gp_state)) |
|
failed = rcu_torture_boost_failed(gp_state, &gp_state_time); |
|
// If we don't have a grace period in flight, start one. |
|
if (!gp_initiated || cur_ops->poll_gp_state(gp_state)) { |
|
gp_state = cur_ops->start_gp_poll(); |
|
gp_initiated = true; |
|
gp_state_time = jiffies; |
|
} |
|
if (stutter_wait("rcu_torture_boost")) { |
|
sched_set_fifo_low(current); |
|
// If the grace period already ended, |
|
// we don't know when that happened, so |
|
// start over. |
|
if (cur_ops->poll_gp_state(gp_state)) |
|
gp_initiated = false; |
|
} |
|
if (torture_must_stop()) |
|
goto checkwait; |
|
} |
|
|
|
// In case the grace period extended beyond the end of the loop. |
|
if (gp_initiated && !failed && !cur_ops->poll_gp_state(gp_state)) |
|
rcu_torture_boost_failed(gp_state, &gp_state_time); |
|
|
|
/* |
|
* 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. |
|
*/ |
|
while (oldstarttime == boost_starttime && !kthread_should_stop()) { |
|
if (mutex_trylock(&boost_mutex)) { |
|
if (oldstarttime == boost_starttime) { |
|
boost_starttime = jiffies + test_boost_interval * HZ; |
|
n_rcu_torture_boosts++; |
|
} |
|
mutex_unlock(&boost_mutex); |
|
break; |
|
} |
|
schedule_timeout_uninterruptible(1); |
|
} |
|
|
|
/* Go do the stutter. */ |
|
checkwait: if (stutter_wait("rcu_torture_boost")) |
|
sched_set_fifo_low(current); |
|
} while (!torture_must_stop()); |
|
|
|
/* Clean up and exit. */ |
|
while (!kthread_should_stop()) { |
|
torture_shutdown_absorb("rcu_torture_boost"); |
|
schedule_timeout_uninterruptible(1); |
|
} |
|
torture_kthread_stopping("rcu_torture_boost"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* 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; |
|
int oldnice = task_nice(current); |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_fqs task started"); |
|
do { |
|
fqs_resume_time = jiffies + fqs_stutter * HZ; |
|
while (time_before(jiffies, fqs_resume_time) && |
|
!kthread_should_stop()) { |
|
schedule_timeout_interruptible(1); |
|
} |
|
fqs_burst_remaining = fqs_duration; |
|
while (fqs_burst_remaining > 0 && |
|
!kthread_should_stop()) { |
|
cur_ops->fqs(); |
|
udelay(fqs_holdoff); |
|
fqs_burst_remaining -= fqs_holdoff; |
|
} |
|
if (stutter_wait("rcu_torture_fqs")) |
|
sched_set_normal(current, oldnice); |
|
} while (!torture_must_stop()); |
|
torture_kthread_stopping("rcu_torture_fqs"); |
|
return 0; |
|
} |
|
|
|
// Used by writers to randomly choose from the available grace-period |
|
// primitives. The only purpose of the initialization is to size the array. |
|
static int synctype[] = { RTWS_DEF_FREE, RTWS_EXP_SYNC, RTWS_COND_GET, RTWS_POLL_GET, RTWS_SYNC }; |
|
static int nsynctypes; |
|
|
|
/* |
|
* Determine which grace-period primitives are available. |
|
*/ |
|
static void rcu_torture_write_types(void) |
|
{ |
|
bool gp_cond1 = gp_cond, gp_exp1 = gp_exp, gp_normal1 = gp_normal; |
|
bool gp_poll1 = gp_poll, gp_sync1 = gp_sync; |
|
|
|
/* Initialize synctype[] array. If none set, take default. */ |
|
if (!gp_cond1 && !gp_exp1 && !gp_normal1 && !gp_poll1 && !gp_sync1) |
|
gp_cond1 = gp_exp1 = gp_normal1 = gp_poll1 = gp_sync1 = true; |
|
if (gp_cond1 && cur_ops->get_gp_state && cur_ops->cond_sync) { |
|
synctype[nsynctypes++] = RTWS_COND_GET; |
|
pr_info("%s: Testing conditional GPs.\n", __func__); |
|
} else if (gp_cond && (!cur_ops->get_gp_state || !cur_ops->cond_sync)) { |
|
pr_alert("%s: gp_cond without primitives.\n", __func__); |
|
} |
|
if (gp_exp1 && cur_ops->exp_sync) { |
|
synctype[nsynctypes++] = RTWS_EXP_SYNC; |
|
pr_info("%s: Testing expedited GPs.\n", __func__); |
|
} else if (gp_exp && !cur_ops->exp_sync) { |
|
pr_alert("%s: gp_exp without primitives.\n", __func__); |
|
} |
|
if (gp_normal1 && cur_ops->deferred_free) { |
|
synctype[nsynctypes++] = RTWS_DEF_FREE; |
|
pr_info("%s: Testing asynchronous GPs.\n", __func__); |
|
} else if (gp_normal && !cur_ops->deferred_free) { |
|
pr_alert("%s: gp_normal without primitives.\n", __func__); |
|
} |
|
if (gp_poll1 && cur_ops->start_gp_poll && cur_ops->poll_gp_state) { |
|
synctype[nsynctypes++] = RTWS_POLL_GET; |
|
pr_info("%s: Testing polling GPs.\n", __func__); |
|
} else if (gp_poll && (!cur_ops->start_gp_poll || !cur_ops->poll_gp_state)) { |
|
pr_alert("%s: gp_poll without primitives.\n", __func__); |
|
} |
|
if (gp_sync1 && cur_ops->sync) { |
|
synctype[nsynctypes++] = RTWS_SYNC; |
|
pr_info("%s: Testing normal GPs.\n", __func__); |
|
} else if (gp_sync && !cur_ops->sync) { |
|
pr_alert("%s: gp_sync without primitives.\n", __func__); |
|
} |
|
} |
|
|
|
/* |
|
* 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) |
|
{ |
|
bool boot_ended; |
|
bool can_expedite = !rcu_gp_is_expedited() && !rcu_gp_is_normal(); |
|
unsigned long cookie; |
|
int expediting = 0; |
|
unsigned long gp_snap; |
|
int i; |
|
int idx; |
|
int oldnice = task_nice(current); |
|
struct rcu_torture *rp; |
|
struct rcu_torture *old_rp; |
|
static DEFINE_TORTURE_RANDOM(rand); |
|
bool stutter_waited; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_writer task started"); |
|
if (!can_expedite) |
|
pr_alert("%s" TORTURE_FLAG |
|
" GP expediting controlled from boot/sysfs for %s.\n", |
|
torture_type, cur_ops->name); |
|
if (WARN_ONCE(nsynctypes == 0, |
|
"rcu_torture_writer: No update-side primitives.\n")) { |
|
/* |
|
* No updates primitives, so don't try updating. |
|
* The resulting test won't be testing much, hence the |
|
* above WARN_ONCE(). |
|
*/ |
|
rcu_torture_writer_state = RTWS_STOPPING; |
|
torture_kthread_stopping("rcu_torture_writer"); |
|
} |
|
|
|
do { |
|
rcu_torture_writer_state = RTWS_FIXED_DELAY; |
|
torture_hrtimeout_us(500, 1000, &rand); |
|
rp = rcu_torture_alloc(); |
|
if (rp == NULL) |
|
continue; |
|
rp->rtort_pipe_count = 0; |
|
rcu_torture_writer_state = RTWS_DELAY; |
|
udelay(torture_random(&rand) & 0x3ff); |
|
rcu_torture_writer_state = RTWS_REPLACE; |
|
old_rp = rcu_dereference_check(rcu_torture_current, |
|
current == writer_task); |
|
rp->rtort_mbtest = 1; |
|
rcu_assign_pointer(rcu_torture_current, rp); |
|
smp_wmb(); /* Mods to old_rp must follow rcu_assign_pointer() */ |
|
if (old_rp) { |
|
i = old_rp->rtort_pipe_count; |
|
if (i > RCU_TORTURE_PIPE_LEN) |
|
i = RCU_TORTURE_PIPE_LEN; |
|
atomic_inc(&rcu_torture_wcount[i]); |
|
WRITE_ONCE(old_rp->rtort_pipe_count, |
|
old_rp->rtort_pipe_count + 1); |
|
if (cur_ops->get_gp_state && cur_ops->poll_gp_state) { |
|
idx = cur_ops->readlock(); |
|
cookie = cur_ops->get_gp_state(); |
|
WARN_ONCE(rcu_torture_writer_state != RTWS_DEF_FREE && |
|
cur_ops->poll_gp_state(cookie), |
|
"%s: Cookie check 1 failed %s(%d) %lu->%lu\n", |
|
__func__, |
|
rcu_torture_writer_state_getname(), |
|
rcu_torture_writer_state, |
|
cookie, cur_ops->get_gp_state()); |
|
cur_ops->readunlock(idx); |
|
} |
|
switch (synctype[torture_random(&rand) % nsynctypes]) { |
|
case RTWS_DEF_FREE: |
|
rcu_torture_writer_state = RTWS_DEF_FREE; |
|
cur_ops->deferred_free(old_rp); |
|
break; |
|
case RTWS_EXP_SYNC: |
|
rcu_torture_writer_state = RTWS_EXP_SYNC; |
|
cur_ops->exp_sync(); |
|
rcu_torture_pipe_update(old_rp); |
|
break; |
|
case RTWS_COND_GET: |
|
rcu_torture_writer_state = RTWS_COND_GET; |
|
gp_snap = cur_ops->get_gp_state(); |
|
torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand); |
|
rcu_torture_writer_state = RTWS_COND_SYNC; |
|
cur_ops->cond_sync(gp_snap); |
|
rcu_torture_pipe_update(old_rp); |
|
break; |
|
case RTWS_POLL_GET: |
|
rcu_torture_writer_state = RTWS_POLL_GET; |
|
gp_snap = cur_ops->start_gp_poll(); |
|
rcu_torture_writer_state = RTWS_POLL_WAIT; |
|
while (!cur_ops->poll_gp_state(gp_snap)) |
|
torture_hrtimeout_jiffies(torture_random(&rand) % 16, |
|
&rand); |
|
rcu_torture_pipe_update(old_rp); |
|
break; |
|
case RTWS_SYNC: |
|
rcu_torture_writer_state = RTWS_SYNC; |
|
cur_ops->sync(); |
|
rcu_torture_pipe_update(old_rp); |
|
break; |
|
default: |
|
WARN_ON_ONCE(1); |
|
break; |
|
} |
|
} |
|
WRITE_ONCE(rcu_torture_current_version, |
|
rcu_torture_current_version + 1); |
|
/* Cycle through nesting levels of rcu_expedite_gp() calls. */ |
|
if (can_expedite && |
|
!(torture_random(&rand) & 0xff & (!!expediting - 1))) { |
|
WARN_ON_ONCE(expediting == 0 && rcu_gp_is_expedited()); |
|
if (expediting >= 0) |
|
rcu_expedite_gp(); |
|
else |
|
rcu_unexpedite_gp(); |
|
if (++expediting > 3) |
|
expediting = -expediting; |
|
} else if (!can_expedite) { /* Disabled during boot, recheck. */ |
|
can_expedite = !rcu_gp_is_expedited() && |
|
!rcu_gp_is_normal(); |
|
} |
|
rcu_torture_writer_state = RTWS_STUTTER; |
|
boot_ended = rcu_inkernel_boot_has_ended(); |
|
stutter_waited = stutter_wait("rcu_torture_writer"); |
|
if (stutter_waited && |
|
!READ_ONCE(rcu_fwd_cb_nodelay) && |
|
!cur_ops->slow_gps && |
|
!torture_must_stop() && |
|
boot_ended) |
|
for (i = 0; i < ARRAY_SIZE(rcu_tortures); i++) |
|
if (list_empty(&rcu_tortures[i].rtort_free) && |
|
rcu_access_pointer(rcu_torture_current) != |
|
&rcu_tortures[i]) { |
|
rcu_ftrace_dump(DUMP_ALL); |
|
WARN(1, "%s: rtort_pipe_count: %d\n", __func__, rcu_tortures[i].rtort_pipe_count); |
|
} |
|
if (stutter_waited) |
|
sched_set_normal(current, oldnice); |
|
} while (!torture_must_stop()); |
|
rcu_torture_current = NULL; // Let stats task know that we are done. |
|
/* Reset expediting back to unexpedited. */ |
|
if (expediting > 0) |
|
expediting = -expediting; |
|
while (can_expedite && expediting++ < 0) |
|
rcu_unexpedite_gp(); |
|
WARN_ON_ONCE(can_expedite && rcu_gp_is_expedited()); |
|
if (!can_expedite) |
|
pr_alert("%s" TORTURE_FLAG |
|
" Dynamic grace-period expediting was disabled.\n", |
|
torture_type); |
|
rcu_torture_writer_state = RTWS_STOPPING; |
|
torture_kthread_stopping("rcu_torture_writer"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* RCU torture fake writer kthread. Repeatedly calls sync, with a random |
|
* delay between calls. |
|
*/ |
|
static int |
|
rcu_torture_fakewriter(void *arg) |
|
{ |
|
unsigned long gp_snap; |
|
DEFINE_TORTURE_RANDOM(rand); |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_fakewriter task started"); |
|
set_user_nice(current, MAX_NICE); |
|
|
|
do { |
|
torture_hrtimeout_jiffies(torture_random(&rand) % 10, &rand); |
|
if (cur_ops->cb_barrier != NULL && |
|
torture_random(&rand) % (nfakewriters * 8) == 0) { |
|
cur_ops->cb_barrier(); |
|
} else { |
|
switch (synctype[torture_random(&rand) % nsynctypes]) { |
|
case RTWS_DEF_FREE: |
|
break; |
|
case RTWS_EXP_SYNC: |
|
cur_ops->exp_sync(); |
|
break; |
|
case RTWS_COND_GET: |
|
gp_snap = cur_ops->get_gp_state(); |
|
torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand); |
|
cur_ops->cond_sync(gp_snap); |
|
break; |
|
case RTWS_POLL_GET: |
|
gp_snap = cur_ops->start_gp_poll(); |
|
while (!cur_ops->poll_gp_state(gp_snap)) { |
|
torture_hrtimeout_jiffies(torture_random(&rand) % 16, |
|
&rand); |
|
} |
|
break; |
|
case RTWS_SYNC: |
|
cur_ops->sync(); |
|
break; |
|
default: |
|
WARN_ON_ONCE(1); |
|
break; |
|
} |
|
} |
|
stutter_wait("rcu_torture_fakewriter"); |
|
} while (!torture_must_stop()); |
|
|
|
torture_kthread_stopping("rcu_torture_fakewriter"); |
|
return 0; |
|
} |
|
|
|
static void rcu_torture_timer_cb(struct rcu_head *rhp) |
|
{ |
|
kfree(rhp); |
|
} |
|
|
|
// Set up and carry out testing of RCU's global memory ordering |
|
static void rcu_torture_reader_do_mbchk(long myid, struct rcu_torture *rtp, |
|
struct torture_random_state *trsp) |
|
{ |
|
unsigned long loops; |
|
int noc = torture_num_online_cpus(); |
|
int rdrchked; |
|
int rdrchker; |
|
struct rcu_torture_reader_check *rtrcp; // Me. |
|
struct rcu_torture_reader_check *rtrcp_assigner; // Assigned us to do checking. |
|
struct rcu_torture_reader_check *rtrcp_chked; // Reader being checked. |
|
struct rcu_torture_reader_check *rtrcp_chker; // Reader doing checking when not me. |
|
|
|
if (myid < 0) |
|
return; // Don't try this from timer handlers. |
|
|
|
// Increment my counter. |
|
rtrcp = &rcu_torture_reader_mbchk[myid]; |
|
WRITE_ONCE(rtrcp->rtc_myloops, rtrcp->rtc_myloops + 1); |
|
|
|
// Attempt to assign someone else some checking work. |
|
rdrchked = torture_random(trsp) % nrealreaders; |
|
rtrcp_chked = &rcu_torture_reader_mbchk[rdrchked]; |
|
rdrchker = torture_random(trsp) % nrealreaders; |
|
rtrcp_chker = &rcu_torture_reader_mbchk[rdrchker]; |
|
if (rdrchked != myid && rdrchked != rdrchker && noc >= rdrchked && noc >= rdrchker && |
|
smp_load_acquire(&rtrcp->rtc_chkrdr) < 0 && // Pairs with smp_store_release below. |
|
!READ_ONCE(rtp->rtort_chkp) && |
|
!smp_load_acquire(&rtrcp_chker->rtc_assigner)) { // Pairs with smp_store_release below. |
|
rtrcp->rtc_chkloops = READ_ONCE(rtrcp_chked->rtc_myloops); |
|
WARN_ON_ONCE(rtrcp->rtc_chkrdr >= 0); |
|
rtrcp->rtc_chkrdr = rdrchked; |
|
WARN_ON_ONCE(rtrcp->rtc_ready); // This gets set after the grace period ends. |
|
if (cmpxchg_relaxed(&rtrcp_chker->rtc_assigner, NULL, rtrcp) || |
|
cmpxchg_relaxed(&rtp->rtort_chkp, NULL, rtrcp)) |
|
(void)cmpxchg_relaxed(&rtrcp_chker->rtc_assigner, rtrcp, NULL); // Back out. |
|
} |
|
|
|
// If assigned some completed work, do it! |
|
rtrcp_assigner = READ_ONCE(rtrcp->rtc_assigner); |
|
if (!rtrcp_assigner || !smp_load_acquire(&rtrcp_assigner->rtc_ready)) |
|
return; // No work or work not yet ready. |
|
rdrchked = rtrcp_assigner->rtc_chkrdr; |
|
if (WARN_ON_ONCE(rdrchked < 0)) |
|
return; |
|
rtrcp_chked = &rcu_torture_reader_mbchk[rdrchked]; |
|
loops = READ_ONCE(rtrcp_chked->rtc_myloops); |
|
atomic_inc(&n_rcu_torture_mbchk_tries); |
|
if (ULONG_CMP_LT(loops, rtrcp_assigner->rtc_chkloops)) |
|
atomic_inc(&n_rcu_torture_mbchk_fail); |
|
rtrcp_assigner->rtc_chkloops = loops + ULONG_MAX / 2; |
|
rtrcp_assigner->rtc_ready = 0; |
|
smp_store_release(&rtrcp->rtc_assigner, NULL); // Someone else can assign us work. |
|
smp_store_release(&rtrcp_assigner->rtc_chkrdr, -1); // Assigner can again assign. |
|
} |
|
|
|
/* |
|
* Do one extension of an RCU read-side critical section using the |
|
* current reader state in readstate (set to zero for initial entry |
|
* to extended critical section), set the new state as specified by |
|
* newstate (set to zero for final exit from extended critical section), |
|
* and random-number-generator state in trsp. If this is neither the |
|
* beginning or end of the critical section and if there was actually a |
|
* change, do a ->read_delay(). |
|
*/ |
|
static void rcutorture_one_extend(int *readstate, int newstate, |
|
struct torture_random_state *trsp, |
|
struct rt_read_seg *rtrsp) |
|
{ |
|
unsigned long flags; |
|
int idxnew = -1; |
|
int idxold = *readstate; |
|
int statesnew = ~*readstate & newstate; |
|
int statesold = *readstate & ~newstate; |
|
|
|
WARN_ON_ONCE(idxold < 0); |
|
WARN_ON_ONCE((idxold >> RCUTORTURE_RDR_SHIFT) > 1); |
|
rtrsp->rt_readstate = newstate; |
|
|
|
/* First, put new protection in place to avoid critical-section gap. */ |
|
if (statesnew & RCUTORTURE_RDR_BH) |
|
local_bh_disable(); |
|
if (statesnew & RCUTORTURE_RDR_IRQ) |
|
local_irq_disable(); |
|
if (statesnew & RCUTORTURE_RDR_PREEMPT) |
|
preempt_disable(); |
|
if (statesnew & RCUTORTURE_RDR_RBH) |
|
rcu_read_lock_bh(); |
|
if (statesnew & RCUTORTURE_RDR_SCHED) |
|
rcu_read_lock_sched(); |
|
if (statesnew & RCUTORTURE_RDR_RCU) |
|
idxnew = cur_ops->readlock() << RCUTORTURE_RDR_SHIFT; |
|
|
|
/* Next, remove old protection, irq first due to bh conflict. */ |
|
if (statesold & RCUTORTURE_RDR_IRQ) |
|
local_irq_enable(); |
|
if (statesold & RCUTORTURE_RDR_BH) |
|
local_bh_enable(); |
|
if (statesold & RCUTORTURE_RDR_PREEMPT) |
|
preempt_enable(); |
|
if (statesold & RCUTORTURE_RDR_RBH) |
|
rcu_read_unlock_bh(); |
|
if (statesold & RCUTORTURE_RDR_SCHED) |
|
rcu_read_unlock_sched(); |
|
if (statesold & RCUTORTURE_RDR_RCU) { |
|
bool lockit = !statesnew && !(torture_random(trsp) & 0xffff); |
|
|
|
if (lockit) |
|
raw_spin_lock_irqsave(¤t->pi_lock, flags); |
|
cur_ops->readunlock(idxold >> RCUTORTURE_RDR_SHIFT); |
|
if (lockit) |
|
raw_spin_unlock_irqrestore(¤t->pi_lock, flags); |
|
} |
|
|
|
/* Delay if neither beginning nor end and there was a change. */ |
|
if ((statesnew || statesold) && *readstate && newstate) |
|
cur_ops->read_delay(trsp, rtrsp); |
|
|
|
/* Update the reader state. */ |
|
if (idxnew == -1) |
|
idxnew = idxold & ~RCUTORTURE_RDR_MASK; |
|
WARN_ON_ONCE(idxnew < 0); |
|
WARN_ON_ONCE((idxnew >> RCUTORTURE_RDR_SHIFT) > 1); |
|
*readstate = idxnew | newstate; |
|
WARN_ON_ONCE((*readstate >> RCUTORTURE_RDR_SHIFT) < 0); |
|
WARN_ON_ONCE((*readstate >> RCUTORTURE_RDR_SHIFT) > 1); |
|
} |
|
|
|
/* Return the biggest extendables mask given current RCU and boot parameters. */ |
|
static int rcutorture_extend_mask_max(void) |
|
{ |
|
int mask; |
|
|
|
WARN_ON_ONCE(extendables & ~RCUTORTURE_MAX_EXTEND); |
|
mask = extendables & RCUTORTURE_MAX_EXTEND & cur_ops->extendables; |
|
mask = mask | RCUTORTURE_RDR_RCU; |
|
return mask; |
|
} |
|
|
|
/* Return a random protection state mask, but with at least one bit set. */ |
|
static int |
|
rcutorture_extend_mask(int oldmask, struct torture_random_state *trsp) |
|
{ |
|
int mask = rcutorture_extend_mask_max(); |
|
unsigned long randmask1 = torture_random(trsp) >> 8; |
|
unsigned long randmask2 = randmask1 >> 3; |
|
|
|
WARN_ON_ONCE(mask >> RCUTORTURE_RDR_SHIFT); |
|
/* Mostly only one bit (need preemption!), sometimes lots of bits. */ |
|
if (!(randmask1 & 0x7)) |
|
mask = mask & randmask2; |
|
else |
|
mask = mask & (1 << (randmask2 % RCUTORTURE_RDR_NBITS)); |
|
/* Can't enable bh w/irq disabled. */ |
|
if ((mask & RCUTORTURE_RDR_IRQ) && |
|
((!(mask & RCUTORTURE_RDR_BH) && (oldmask & RCUTORTURE_RDR_BH)) || |
|
(!(mask & RCUTORTURE_RDR_RBH) && (oldmask & RCUTORTURE_RDR_RBH)))) |
|
mask |= RCUTORTURE_RDR_BH | RCUTORTURE_RDR_RBH; |
|
return mask ?: RCUTORTURE_RDR_RCU; |
|
} |
|
|
|
/* |
|
* Do a randomly selected number of extensions of an existing RCU read-side |
|
* critical section. |
|
*/ |
|
static struct rt_read_seg * |
|
rcutorture_loop_extend(int *readstate, struct torture_random_state *trsp, |
|
struct rt_read_seg *rtrsp) |
|
{ |
|
int i; |
|
int j; |
|
int mask = rcutorture_extend_mask_max(); |
|
|
|
WARN_ON_ONCE(!*readstate); /* -Existing- RCU read-side critsect! */ |
|
if (!((mask - 1) & mask)) |
|
return rtrsp; /* Current RCU reader not extendable. */ |
|
/* Bias towards larger numbers of loops. */ |
|
i = (torture_random(trsp) >> 3); |
|
i = ((i | (i >> 3)) & RCUTORTURE_RDR_MAX_LOOPS) + 1; |
|
for (j = 0; j < i; j++) { |
|
mask = rcutorture_extend_mask(*readstate, trsp); |
|
rcutorture_one_extend(readstate, mask, trsp, &rtrsp[j]); |
|
} |
|
return &rtrsp[j]; |
|
} |
|
|
|
/* |
|
* Do one read-side critical section, returning false if there was |
|
* no data to read. Can be invoked both from process context and |
|
* from a timer handler. |
|
*/ |
|
static bool rcu_torture_one_read(struct torture_random_state *trsp, long myid) |
|
{ |
|
unsigned long cookie; |
|
int i; |
|
unsigned long started; |
|
unsigned long completed; |
|
int newstate; |
|
struct rcu_torture *p; |
|
int pipe_count; |
|
int readstate = 0; |
|
struct rt_read_seg rtseg[RCUTORTURE_RDR_MAX_SEGS] = { { 0 } }; |
|
struct rt_read_seg *rtrsp = &rtseg[0]; |
|
struct rt_read_seg *rtrsp1; |
|
unsigned long long ts; |
|
|
|
WARN_ON_ONCE(!rcu_is_watching()); |
|
newstate = rcutorture_extend_mask(readstate, trsp); |
|
rcutorture_one_extend(&readstate, newstate, trsp, rtrsp++); |
|
if (cur_ops->get_gp_state && cur_ops->poll_gp_state) |
|
cookie = cur_ops->get_gp_state(); |
|
started = cur_ops->get_gp_seq(); |
|
ts = rcu_trace_clock_local(); |
|
p = rcu_dereference_check(rcu_torture_current, |
|
!cur_ops->readlock_held || cur_ops->readlock_held()); |
|
if (p == NULL) { |
|
/* Wait for rcu_torture_writer to get underway */ |
|
rcutorture_one_extend(&readstate, 0, trsp, rtrsp); |
|
return false; |
|
} |
|
if (p->rtort_mbtest == 0) |
|
atomic_inc(&n_rcu_torture_mberror); |
|
rcu_torture_reader_do_mbchk(myid, p, trsp); |
|
rtrsp = rcutorture_loop_extend(&readstate, trsp, rtrsp); |
|
preempt_disable(); |
|
pipe_count = READ_ONCE(p->rtort_pipe_count); |
|
if (pipe_count > RCU_TORTURE_PIPE_LEN) { |
|
/* Should not happen, but... */ |
|
pipe_count = RCU_TORTURE_PIPE_LEN; |
|
} |
|
completed = cur_ops->get_gp_seq(); |
|
if (pipe_count > 1) { |
|
do_trace_rcu_torture_read(cur_ops->name, &p->rtort_rcu, |
|
ts, started, completed); |
|
rcu_ftrace_dump(DUMP_ALL); |
|
} |
|
__this_cpu_inc(rcu_torture_count[pipe_count]); |
|
completed = rcutorture_seq_diff(completed, started); |
|
if (completed > RCU_TORTURE_PIPE_LEN) { |
|
/* Should not happen, but... */ |
|
completed = RCU_TORTURE_PIPE_LEN; |
|
} |
|
__this_cpu_inc(rcu_torture_batch[completed]); |
|
preempt_enable(); |
|
if (cur_ops->get_gp_state && cur_ops->poll_gp_state) |
|
WARN_ONCE(cur_ops->poll_gp_state(cookie), |
|
"%s: Cookie check 2 failed %s(%d) %lu->%lu\n", |
|
__func__, |
|
rcu_torture_writer_state_getname(), |
|
rcu_torture_writer_state, |
|
cookie, cur_ops->get_gp_state()); |
|
rcutorture_one_extend(&readstate, 0, trsp, rtrsp); |
|
WARN_ON_ONCE(readstate & RCUTORTURE_RDR_MASK); |
|
// This next splat is expected behavior if leakpointer, especially |
|
// for CONFIG_RCU_STRICT_GRACE_PERIOD=y kernels. |
|
WARN_ON_ONCE(leakpointer && READ_ONCE(p->rtort_pipe_count) > 1); |
|
|
|
/* If error or close call, record the sequence of reader protections. */ |
|
if ((pipe_count > 1 || completed > 1) && !xchg(&err_segs_recorded, 1)) { |
|
i = 0; |
|
for (rtrsp1 = &rtseg[0]; rtrsp1 < rtrsp; rtrsp1++) |
|
err_segs[i++] = *rtrsp1; |
|
rt_read_nsegs = i; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_timer_rand); |
|
|
|
/* |
|
* 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(struct timer_list *unused) |
|
{ |
|
atomic_long_inc(&n_rcu_torture_timers); |
|
(void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_timer_rand), -1); |
|
|
|
/* Test call_rcu() invocation from interrupt handler. */ |
|
if (cur_ops->call) { |
|
struct rcu_head *rhp = kmalloc(sizeof(*rhp), GFP_NOWAIT); |
|
|
|
if (rhp) |
|
cur_ops->call(rhp, rcu_torture_timer_cb); |
|
} |
|
} |
|
|
|
/* |
|
* 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) |
|
{ |
|
unsigned long lastsleep = jiffies; |
|
long myid = (long)arg; |
|
int mynumonline = myid; |
|
DEFINE_TORTURE_RANDOM(rand); |
|
struct timer_list t; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_reader task started"); |
|
set_user_nice(current, MAX_NICE); |
|
if (irqreader && cur_ops->irq_capable) |
|
timer_setup_on_stack(&t, rcu_torture_timer, 0); |
|
tick_dep_set_task(current, TICK_DEP_BIT_RCU); |
|
do { |
|
if (irqreader && cur_ops->irq_capable) { |
|
if (!timer_pending(&t)) |
|
mod_timer(&t, jiffies + 1); |
|
} |
|
if (!rcu_torture_one_read(&rand, myid) && !torture_must_stop()) |
|
schedule_timeout_interruptible(HZ); |
|
if (time_after(jiffies, lastsleep) && !torture_must_stop()) { |
|
torture_hrtimeout_us(500, 1000, &rand); |
|
lastsleep = jiffies + 10; |
|
} |
|
while (torture_num_online_cpus() < mynumonline && !torture_must_stop()) |
|
schedule_timeout_interruptible(HZ / 5); |
|
stutter_wait("rcu_torture_reader"); |
|
} while (!torture_must_stop()); |
|
if (irqreader && cur_ops->irq_capable) { |
|
del_timer_sync(&t); |
|
destroy_timer_on_stack(&t); |
|
} |
|
tick_dep_clear_task(current, TICK_DEP_BIT_RCU); |
|
torture_kthread_stopping("rcu_torture_reader"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Randomly Toggle CPUs' callback-offload state. This uses hrtimers to |
|
* increase race probabilities and fuzzes the interval between toggling. |
|
*/ |
|
static int rcu_nocb_toggle(void *arg) |
|
{ |
|
int cpu; |
|
int maxcpu = -1; |
|
int oldnice = task_nice(current); |
|
long r; |
|
DEFINE_TORTURE_RANDOM(rand); |
|
ktime_t toggle_delay; |
|
unsigned long toggle_fuzz; |
|
ktime_t toggle_interval = ms_to_ktime(nocbs_toggle); |
|
|
|
VERBOSE_TOROUT_STRING("rcu_nocb_toggle task started"); |
|
while (!rcu_inkernel_boot_has_ended()) |
|
schedule_timeout_interruptible(HZ / 10); |
|
for_each_online_cpu(cpu) |
|
maxcpu = cpu; |
|
WARN_ON(maxcpu < 0); |
|
if (toggle_interval > ULONG_MAX) |
|
toggle_fuzz = ULONG_MAX >> 3; |
|
else |
|
toggle_fuzz = toggle_interval >> 3; |
|
if (toggle_fuzz <= 0) |
|
toggle_fuzz = NSEC_PER_USEC; |
|
do { |
|
r = torture_random(&rand); |
|
cpu = (r >> 4) % (maxcpu + 1); |
|
if (r & 0x1) { |
|
rcu_nocb_cpu_offload(cpu); |
|
atomic_long_inc(&n_nocb_offload); |
|
} else { |
|
rcu_nocb_cpu_deoffload(cpu); |
|
atomic_long_inc(&n_nocb_deoffload); |
|
} |
|
toggle_delay = torture_random(&rand) % toggle_fuzz + toggle_interval; |
|
set_current_state(TASK_INTERRUPTIBLE); |
|
schedule_hrtimeout(&toggle_delay, HRTIMER_MODE_REL); |
|
if (stutter_wait("rcu_nocb_toggle")) |
|
sched_set_normal(current, oldnice); |
|
} while (!torture_must_stop()); |
|
torture_kthread_stopping("rcu_nocb_toggle"); |
|
return 0; |
|
} |
|
|
|
/* |
|
* 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). |
|
*/ |
|
static void |
|
rcu_torture_stats_print(void) |
|
{ |
|
int cpu; |
|
int i; |
|
long pipesummary[RCU_TORTURE_PIPE_LEN + 1] = { 0 }; |
|
long batchsummary[RCU_TORTURE_PIPE_LEN + 1] = { 0 }; |
|
struct rcu_torture *rtcp; |
|
static unsigned long rtcv_snap = ULONG_MAX; |
|
static bool splatted; |
|
struct task_struct *wtp; |
|
|
|
for_each_possible_cpu(cpu) { |
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { |
|
pipesummary[i] += READ_ONCE(per_cpu(rcu_torture_count, cpu)[i]); |
|
batchsummary[i] += READ_ONCE(per_cpu(rcu_torture_batch, cpu)[i]); |
|
} |
|
} |
|
for (i = RCU_TORTURE_PIPE_LEN - 1; i >= 0; i--) { |
|
if (pipesummary[i] != 0) |
|
break; |
|
} |
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG); |
|
rtcp = rcu_access_pointer(rcu_torture_current); |
|
pr_cont("rtc: %p %s: %lu tfle: %d rta: %d rtaf: %d rtf: %d ", |
|
rtcp, |
|
rtcp && !rcu_stall_is_suppressed_at_boot() ? "ver" : "VER", |
|
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 rtmbkf: %d/%d rtbe: %ld rtbke: %ld rtbre: %ld ", |
|
atomic_read(&n_rcu_torture_mberror), |
|
atomic_read(&n_rcu_torture_mbchk_fail), atomic_read(&n_rcu_torture_mbchk_tries), |
|
n_rcu_torture_barrier_error, |
|
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, |
|
atomic_long_read(&n_rcu_torture_timers)); |
|
torture_onoff_stats(); |
|
pr_cont("barrier: %ld/%ld:%ld ", |
|
data_race(n_barrier_successes), |
|
data_race(n_barrier_attempts), |
|
data_race(n_rcu_torture_barrier_error)); |
|
pr_cont("read-exits: %ld ", data_race(n_read_exits)); // Statistic. |
|
pr_cont("nocb-toggles: %ld:%ld\n", |
|
atomic_long_read(&n_nocb_offload), atomic_long_read(&n_nocb_deoffload)); |
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG); |
|
if (atomic_read(&n_rcu_torture_mberror) || |
|
atomic_read(&n_rcu_torture_mbchk_fail) || |
|
n_rcu_torture_barrier_error || n_rcu_torture_boost_ktrerror || |
|
n_rcu_torture_boost_rterror || n_rcu_torture_boost_failure || |
|
i > 1) { |
|
pr_cont("%s", "!!! "); |
|
atomic_inc(&n_rcu_torture_error); |
|
WARN_ON_ONCE(atomic_read(&n_rcu_torture_mberror)); |
|
WARN_ON_ONCE(atomic_read(&n_rcu_torture_mbchk_fail)); |
|
WARN_ON_ONCE(n_rcu_torture_barrier_error); // rcu_barrier() |
|
WARN_ON_ONCE(n_rcu_torture_boost_ktrerror); // no boost kthread |
|
WARN_ON_ONCE(n_rcu_torture_boost_rterror); // can't set RT prio |
|
WARN_ON_ONCE(n_rcu_torture_boost_failure); // boost failed (TIMER_SOFTIRQ RT prio?) |
|
WARN_ON_ONCE(i > 1); // Too-short grace period |
|
} |
|
pr_cont("Reader Pipe: "); |
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) |
|
pr_cont(" %ld", pipesummary[i]); |
|
pr_cont("\n"); |
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG); |
|
pr_cont("Reader Batch: "); |
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) |
|
pr_cont(" %ld", batchsummary[i]); |
|
pr_cont("\n"); |
|
|
|
pr_alert("%s%s ", torture_type, TORTURE_FLAG); |
|
pr_cont("Free-Block Circulation: "); |
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) { |
|
pr_cont(" %d", atomic_read(&rcu_torture_wcount[i])); |
|
} |
|
pr_cont("\n"); |
|
|
|
if (cur_ops->stats) |
|
cur_ops->stats(); |
|
if (rtcv_snap == rcu_torture_current_version && |
|
rcu_access_pointer(rcu_torture_current) && |
|
!rcu_stall_is_suppressed()) { |
|
int __maybe_unused flags = 0; |
|
unsigned long __maybe_unused gp_seq = 0; |
|
|
|
rcutorture_get_gp_data(cur_ops->ttype, |
|
&flags, &gp_seq); |
|
srcutorture_get_gp_data(cur_ops->ttype, srcu_ctlp, |
|
&flags, &gp_seq); |
|
wtp = READ_ONCE(writer_task); |
|
pr_alert("??? Writer stall state %s(%d) g%lu f%#x ->state %#x cpu %d\n", |
|
rcu_torture_writer_state_getname(), |
|
rcu_torture_writer_state, gp_seq, flags, |
|
wtp == NULL ? ~0U : wtp->__state, |
|
wtp == NULL ? -1 : (int)task_cpu(wtp)); |
|
if (!splatted && wtp) { |
|
sched_show_task(wtp); |
|
splatted = true; |
|
} |
|
if (cur_ops->gp_kthread_dbg) |
|
cur_ops->gp_kthread_dbg(); |
|
rcu_ftrace_dump(DUMP_ALL); |
|
} |
|
rtcv_snap = rcu_torture_current_version; |
|
} |
|
|
|
/* |
|
* Periodically prints torture statistics, if periodic statistics printing |
|
* was specified via the stat_interval module parameter. |
|
*/ |
|
static int |
|
rcu_torture_stats(void *arg) |
|
{ |
|
VERBOSE_TOROUT_STRING("rcu_torture_stats task started"); |
|
do { |
|
schedule_timeout_interruptible(stat_interval * HZ); |
|
rcu_torture_stats_print(); |
|
torture_shutdown_absorb("rcu_torture_stats"); |
|
} while (!torture_must_stop()); |
|
torture_kthread_stopping("rcu_torture_stats"); |
|
return 0; |
|
} |
|
|
|
/* Test mem_dump_obj() and friends. */ |
|
static void rcu_torture_mem_dump_obj(void) |
|
{ |
|
struct rcu_head *rhp; |
|
struct kmem_cache *kcp; |
|
static int z; |
|
|
|
kcp = kmem_cache_create("rcuscale", 136, 8, SLAB_STORE_USER, NULL); |
|
rhp = kmem_cache_alloc(kcp, GFP_KERNEL); |
|
pr_alert("mem_dump_obj() slab test: rcu_torture_stats = %px, &rhp = %px, rhp = %px, &z = %px\n", stats_task, &rhp, rhp, &z); |
|
pr_alert("mem_dump_obj(ZERO_SIZE_PTR):"); |
|
mem_dump_obj(ZERO_SIZE_PTR); |
|
pr_alert("mem_dump_obj(NULL):"); |
|
mem_dump_obj(NULL); |
|
pr_alert("mem_dump_obj(%px):", &rhp); |
|
mem_dump_obj(&rhp); |
|
pr_alert("mem_dump_obj(%px):", rhp); |
|
mem_dump_obj(rhp); |
|
pr_alert("mem_dump_obj(%px):", &rhp->func); |
|
mem_dump_obj(&rhp->func); |
|
pr_alert("mem_dump_obj(%px):", &z); |
|
mem_dump_obj(&z); |
|
kmem_cache_free(kcp, rhp); |
|
kmem_cache_destroy(kcp); |
|
rhp = kmalloc(sizeof(*rhp), GFP_KERNEL); |
|
pr_alert("mem_dump_obj() kmalloc test: rcu_torture_stats = %px, &rhp = %px, rhp = %px\n", stats_task, &rhp, rhp); |
|
pr_alert("mem_dump_obj(kmalloc %px):", rhp); |
|
mem_dump_obj(rhp); |
|
pr_alert("mem_dump_obj(kmalloc %px):", &rhp->func); |
|
mem_dump_obj(&rhp->func); |
|
kfree(rhp); |
|
rhp = vmalloc(4096); |
|
pr_alert("mem_dump_obj() vmalloc test: rcu_torture_stats = %px, &rhp = %px, rhp = %px\n", stats_task, &rhp, rhp); |
|
pr_alert("mem_dump_obj(vmalloc %px):", rhp); |
|
mem_dump_obj(rhp); |
|
pr_alert("mem_dump_obj(vmalloc %px):", &rhp->func); |
|
mem_dump_obj(&rhp->func); |
|
vfree(rhp); |
|
} |
|
|
|
static void |
|
rcu_torture_print_module_parms(struct rcu_torture_ops *cur_ops, const char *tag) |
|
{ |
|
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 " |
|
"stall_cpu=%d stall_cpu_holdoff=%d stall_cpu_irqsoff=%d " |
|
"stall_cpu_block=%d " |
|
"n_barrier_cbs=%d " |
|
"onoff_interval=%d onoff_holdoff=%d " |
|
"read_exit_delay=%d read_exit_burst=%d " |
|
"nocbs_nthreads=%d nocbs_toggle=%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, |
|
stall_cpu, stall_cpu_holdoff, stall_cpu_irqsoff, |
|
stall_cpu_block, |
|
n_barrier_cbs, |
|
onoff_interval, onoff_holdoff, |
|
read_exit_delay, read_exit_burst, |
|
nocbs_nthreads, nocbs_toggle); |
|
} |
|
|
|
static int rcutorture_booster_cleanup(unsigned int cpu) |
|
{ |
|
struct task_struct *t; |
|
|
|
if (boost_tasks[cpu] == NULL) |
|
return 0; |
|
mutex_lock(&boost_mutex); |
|
t = boost_tasks[cpu]; |
|
boost_tasks[cpu] = NULL; |
|
rcu_torture_enable_rt_throttle(); |
|
mutex_unlock(&boost_mutex); |
|
|
|
/* This must be outside of the mutex, otherwise deadlock! */ |
|
torture_stop_kthread(rcu_torture_boost, t); |
|
return 0; |
|
} |
|
|
|
static int rcutorture_booster_init(unsigned 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); |
|
rcu_torture_disable_rt_throttle(); |
|
VERBOSE_TOROUT_STRING("Creating rcu_torture_boost task"); |
|
boost_tasks[cpu] = kthread_create_on_node(rcu_torture_boost, NULL, |
|
cpu_to_node(cpu), |
|
"rcu_torture_boost"); |
|
if (IS_ERR(boost_tasks[cpu])) { |
|
retval = PTR_ERR(boost_tasks[cpu]); |
|
VERBOSE_TOROUT_STRING("rcu_torture_boost task create failed"); |
|
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; |
|
} |
|
|
|
/* |
|
* CPU-stall kthread. It waits as specified by stall_cpu_holdoff, then |
|
* induces a CPU stall for the time specified by stall_cpu. |
|
*/ |
|
static int rcu_torture_stall(void *args) |
|
{ |
|
int idx; |
|
unsigned long stop_at; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_stall task started"); |
|
if (stall_cpu_holdoff > 0) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_stall begin holdoff"); |
|
schedule_timeout_interruptible(stall_cpu_holdoff * HZ); |
|
VERBOSE_TOROUT_STRING("rcu_torture_stall end holdoff"); |
|
} |
|
if (!kthread_should_stop() && stall_gp_kthread > 0) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_stall begin GP stall"); |
|
rcu_gp_set_torture_wait(stall_gp_kthread * HZ); |
|
for (idx = 0; idx < stall_gp_kthread + 2; idx++) { |
|
if (kthread_should_stop()) |
|
break; |
|
schedule_timeout_uninterruptible(HZ); |
|
} |
|
} |
|
if (!kthread_should_stop() && stall_cpu > 0) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_stall begin CPU stall"); |
|
stop_at = ktime_get_seconds() + stall_cpu; |
|
/* RCU CPU stall is expected behavior in following code. */ |
|
idx = cur_ops->readlock(); |
|
if (stall_cpu_irqsoff) |
|
local_irq_disable(); |
|
else if (!stall_cpu_block) |
|
preempt_disable(); |
|
pr_alert("%s start on CPU %d.\n", |
|
__func__, raw_smp_processor_id()); |
|
while (ULONG_CMP_LT((unsigned long)ktime_get_seconds(), |
|
stop_at)) |
|
if (stall_cpu_block) { |
|
#ifdef CONFIG_PREEMPTION |
|
preempt_schedule(); |
|
#else |
|
schedule_timeout_uninterruptible(HZ); |
|
#endif |
|
} |
|
if (stall_cpu_irqsoff) |
|
local_irq_enable(); |
|
else if (!stall_cpu_block) |
|
preempt_enable(); |
|
cur_ops->readunlock(idx); |
|
} |
|
pr_alert("%s end.\n", __func__); |
|
torture_shutdown_absorb("rcu_torture_stall"); |
|
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 && stall_gp_kthread <= 0) |
|
return 0; |
|
return torture_create_kthread(rcu_torture_stall, NULL, stall_task); |
|
} |
|
|
|
/* State structure for forward-progress self-propagating RCU callback. */ |
|
struct fwd_cb_state { |
|
struct rcu_head rh; |
|
int stop; |
|
}; |
|
|
|
/* |
|
* Forward-progress self-propagating RCU callback function. Because |
|
* callbacks run from softirq, this function is an implicit RCU read-side |
|
* critical section. |
|
*/ |
|
static void rcu_torture_fwd_prog_cb(struct rcu_head *rhp) |
|
{ |
|
struct fwd_cb_state *fcsp = container_of(rhp, struct fwd_cb_state, rh); |
|
|
|
if (READ_ONCE(fcsp->stop)) { |
|
WRITE_ONCE(fcsp->stop, 2); |
|
return; |
|
} |
|
cur_ops->call(&fcsp->rh, rcu_torture_fwd_prog_cb); |
|
} |
|
|
|
/* State for continuous-flood RCU callbacks. */ |
|
struct rcu_fwd_cb { |
|
struct rcu_head rh; |
|
struct rcu_fwd_cb *rfc_next; |
|
struct rcu_fwd *rfc_rfp; |
|
int rfc_gps; |
|
}; |
|
|
|
#define MAX_FWD_CB_JIFFIES (8 * HZ) /* Maximum CB test duration. */ |
|
#define MIN_FWD_CB_LAUNDERS 3 /* This many CB invocations to count. */ |
|
#define MIN_FWD_CBS_LAUNDERED 100 /* Number of counted CBs. */ |
|
#define FWD_CBS_HIST_DIV 10 /* Histogram buckets/second. */ |
|
#define N_LAUNDERS_HIST (2 * MAX_FWD_CB_JIFFIES / (HZ / FWD_CBS_HIST_DIV)) |
|
|
|
struct rcu_launder_hist { |
|
long n_launders; |
|
unsigned long launder_gp_seq; |
|
}; |
|
|
|
struct rcu_fwd { |
|
spinlock_t rcu_fwd_lock; |
|
struct rcu_fwd_cb *rcu_fwd_cb_head; |
|
struct rcu_fwd_cb **rcu_fwd_cb_tail; |
|
long n_launders_cb; |
|
unsigned long rcu_fwd_startat; |
|
struct rcu_launder_hist n_launders_hist[N_LAUNDERS_HIST]; |
|
unsigned long rcu_launder_gp_seq_start; |
|
}; |
|
|
|
static DEFINE_MUTEX(rcu_fwd_mutex); |
|
static struct rcu_fwd *rcu_fwds; |
|
static bool rcu_fwd_emergency_stop; |
|
|
|
static void rcu_torture_fwd_cb_hist(struct rcu_fwd *rfp) |
|
{ |
|
unsigned long gps; |
|
unsigned long gps_old; |
|
int i; |
|
int j; |
|
|
|
for (i = ARRAY_SIZE(rfp->n_launders_hist) - 1; i > 0; i--) |
|
if (rfp->n_launders_hist[i].n_launders > 0) |
|
break; |
|
pr_alert("%s: Callback-invocation histogram (duration %lu jiffies):", |
|
__func__, jiffies - rfp->rcu_fwd_startat); |
|
gps_old = rfp->rcu_launder_gp_seq_start; |
|
for (j = 0; j <= i; j++) { |
|
gps = rfp->n_launders_hist[j].launder_gp_seq; |
|
pr_cont(" %ds/%d: %ld:%ld", |
|
j + 1, FWD_CBS_HIST_DIV, |
|
rfp->n_launders_hist[j].n_launders, |
|
rcutorture_seq_diff(gps, gps_old)); |
|
gps_old = gps; |
|
} |
|
pr_cont("\n"); |
|
} |
|
|
|
/* Callback function for continuous-flood RCU callbacks. */ |
|
static void rcu_torture_fwd_cb_cr(struct rcu_head *rhp) |
|
{ |
|
unsigned long flags; |
|
int i; |
|
struct rcu_fwd_cb *rfcp = container_of(rhp, struct rcu_fwd_cb, rh); |
|
struct rcu_fwd_cb **rfcpp; |
|
struct rcu_fwd *rfp = rfcp->rfc_rfp; |
|
|
|
rfcp->rfc_next = NULL; |
|
rfcp->rfc_gps++; |
|
spin_lock_irqsave(&rfp->rcu_fwd_lock, flags); |
|
rfcpp = rfp->rcu_fwd_cb_tail; |
|
rfp->rcu_fwd_cb_tail = &rfcp->rfc_next; |
|
WRITE_ONCE(*rfcpp, rfcp); |
|
WRITE_ONCE(rfp->n_launders_cb, rfp->n_launders_cb + 1); |
|
i = ((jiffies - rfp->rcu_fwd_startat) / (HZ / FWD_CBS_HIST_DIV)); |
|
if (i >= ARRAY_SIZE(rfp->n_launders_hist)) |
|
i = ARRAY_SIZE(rfp->n_launders_hist) - 1; |
|
rfp->n_launders_hist[i].n_launders++; |
|
rfp->n_launders_hist[i].launder_gp_seq = cur_ops->get_gp_seq(); |
|
spin_unlock_irqrestore(&rfp->rcu_fwd_lock, flags); |
|
} |
|
|
|
// Give the scheduler a chance, even on nohz_full CPUs. |
|
static void rcu_torture_fwd_prog_cond_resched(unsigned long iter) |
|
{ |
|
if (IS_ENABLED(CONFIG_PREEMPTION) && IS_ENABLED(CONFIG_NO_HZ_FULL)) { |
|
// Real call_rcu() floods hit userspace, so emulate that. |
|
if (need_resched() || (iter & 0xfff)) |
|
schedule(); |
|
return; |
|
} |
|
// No userspace emulation: CB invocation throttles call_rcu() |
|
cond_resched(); |
|
} |
|
|
|
/* |
|
* Free all callbacks on the rcu_fwd_cb_head list, either because the |
|
* test is over or because we hit an OOM event. |
|
*/ |
|
static unsigned long rcu_torture_fwd_prog_cbfree(struct rcu_fwd *rfp) |
|
{ |
|
unsigned long flags; |
|
unsigned long freed = 0; |
|
struct rcu_fwd_cb *rfcp; |
|
|
|
for (;;) { |
|
spin_lock_irqsave(&rfp->rcu_fwd_lock, flags); |
|
rfcp = rfp->rcu_fwd_cb_head; |
|
if (!rfcp) { |
|
spin_unlock_irqrestore(&rfp->rcu_fwd_lock, flags); |
|
break; |
|
} |
|
rfp->rcu_fwd_cb_head = rfcp->rfc_next; |
|
if (!rfp->rcu_fwd_cb_head) |
|
rfp->rcu_fwd_cb_tail = &rfp->rcu_fwd_cb_head; |
|
spin_unlock_irqrestore(&rfp->rcu_fwd_lock, flags); |
|
kfree(rfcp); |
|
freed++; |
|
rcu_torture_fwd_prog_cond_resched(freed); |
|
if (tick_nohz_full_enabled()) { |
|
local_irq_save(flags); |
|
rcu_momentary_dyntick_idle(); |
|
local_irq_restore(flags); |
|
} |
|
} |
|
return freed; |
|
} |
|
|
|
/* Carry out need_resched()/cond_resched() forward-progress testing. */ |
|
static void rcu_torture_fwd_prog_nr(struct rcu_fwd *rfp, |
|
int *tested, int *tested_tries) |
|
{ |
|
unsigned long cver; |
|
unsigned long dur; |
|
struct fwd_cb_state fcs; |
|
unsigned long gps; |
|
int idx; |
|
int sd; |
|
int sd4; |
|
bool selfpropcb = false; |
|
unsigned long stopat; |
|
static DEFINE_TORTURE_RANDOM(trs); |
|
|
|
if (!cur_ops->sync) |
|
return; // Cannot do need_resched() forward progress testing without ->sync. |
|
if (cur_ops->call && cur_ops->cb_barrier) { |
|
init_rcu_head_on_stack(&fcs.rh); |
|
selfpropcb = true; |
|
} |
|
|
|
/* Tight loop containing cond_resched(). */ |
|
WRITE_ONCE(rcu_fwd_cb_nodelay, true); |
|
cur_ops->sync(); /* Later readers see above write. */ |
|
if (selfpropcb) { |
|
WRITE_ONCE(fcs.stop, 0); |
|
cur_ops->call(&fcs.rh, rcu_torture_fwd_prog_cb); |
|
} |
|
cver = READ_ONCE(rcu_torture_current_version); |
|
gps = cur_ops->get_gp_seq(); |
|
sd = cur_ops->stall_dur() + 1; |
|
sd4 = (sd + fwd_progress_div - 1) / fwd_progress_div; |
|
dur = sd4 + torture_random(&trs) % (sd - sd4); |
|
WRITE_ONCE(rfp->rcu_fwd_startat, jiffies); |
|
stopat = rfp->rcu_fwd_startat + dur; |
|
while (time_before(jiffies, stopat) && |
|
!shutdown_time_arrived() && |
|
!READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) { |
|
idx = cur_ops->readlock(); |
|
udelay(10); |
|
cur_ops->readunlock(idx); |
|
if (!fwd_progress_need_resched || need_resched()) |
|
cond_resched(); |
|
} |
|
(*tested_tries)++; |
|
if (!time_before(jiffies, stopat) && |
|
!shutdown_time_arrived() && |
|
!READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) { |
|
(*tested)++; |
|
cver = READ_ONCE(rcu_torture_current_version) - cver; |
|
gps = rcutorture_seq_diff(cur_ops->get_gp_seq(), gps); |
|
WARN_ON(!cver && gps < 2); |
|
pr_alert("%s: Duration %ld cver %ld gps %ld\n", __func__, dur, cver, gps); |
|
} |
|
if (selfpropcb) { |
|
WRITE_ONCE(fcs.stop, 1); |
|
cur_ops->sync(); /* Wait for running CB to complete. */ |
|
cur_ops->cb_barrier(); /* Wait for queued callbacks. */ |
|
} |
|
|
|
if (selfpropcb) { |
|
WARN_ON(READ_ONCE(fcs.stop) != 2); |
|
destroy_rcu_head_on_stack(&fcs.rh); |
|
} |
|
schedule_timeout_uninterruptible(HZ / 10); /* Let kthreads recover. */ |
|
WRITE_ONCE(rcu_fwd_cb_nodelay, false); |
|
} |
|
|
|
/* Carry out call_rcu() forward-progress testing. */ |
|
static void rcu_torture_fwd_prog_cr(struct rcu_fwd *rfp) |
|
{ |
|
unsigned long cver; |
|
unsigned long flags; |
|
unsigned long gps; |
|
int i; |
|
long n_launders; |
|
long n_launders_cb_snap; |
|
long n_launders_sa; |
|
long n_max_cbs; |
|
long n_max_gps; |
|
struct rcu_fwd_cb *rfcp; |
|
struct rcu_fwd_cb *rfcpn; |
|
unsigned long stopat; |
|
unsigned long stoppedat; |
|
|
|
if (READ_ONCE(rcu_fwd_emergency_stop)) |
|
return; /* Get out of the way quickly, no GP wait! */ |
|
if (!cur_ops->call) |
|
return; /* Can't do call_rcu() fwd prog without ->call. */ |
|
|
|
/* Loop continuously posting RCU callbacks. */ |
|
WRITE_ONCE(rcu_fwd_cb_nodelay, true); |
|
cur_ops->sync(); /* Later readers see above write. */ |
|
WRITE_ONCE(rfp->rcu_fwd_startat, jiffies); |
|
stopat = rfp->rcu_fwd_startat + MAX_FWD_CB_JIFFIES; |
|
n_launders = 0; |
|
rfp->n_launders_cb = 0; // Hoist initialization for multi-kthread |
|
n_launders_sa = 0; |
|
n_max_cbs = 0; |
|
n_max_gps = 0; |
|
for (i = 0; i < ARRAY_SIZE(rfp->n_launders_hist); i++) |
|
rfp->n_launders_hist[i].n_launders = 0; |
|
cver = READ_ONCE(rcu_torture_current_version); |
|
gps = cur_ops->get_gp_seq(); |
|
rfp->rcu_launder_gp_seq_start = gps; |
|
tick_dep_set_task(current, TICK_DEP_BIT_RCU); |
|
while (time_before(jiffies, stopat) && |
|
!shutdown_time_arrived() && |
|
!READ_ONCE(rcu_fwd_emergency_stop) && !torture_must_stop()) { |
|
rfcp = READ_ONCE(rfp->rcu_fwd_cb_head); |
|
rfcpn = NULL; |
|
if (rfcp) |
|
rfcpn = READ_ONCE(rfcp->rfc_next); |
|
if (rfcpn) { |
|
if (rfcp->rfc_gps >= MIN_FWD_CB_LAUNDERS && |
|
++n_max_gps >= MIN_FWD_CBS_LAUNDERED) |
|
break; |
|
rfp->rcu_fwd_cb_head = rfcpn; |
|
n_launders++; |
|
n_launders_sa++; |
|
} else { |
|
rfcp = kmalloc(sizeof(*rfcp), GFP_KERNEL); |
|
if (WARN_ON_ONCE(!rfcp)) { |
|
schedule_timeout_interruptible(1); |
|
continue; |
|
} |
|
n_max_cbs++; |
|
n_launders_sa = 0; |
|
rfcp->rfc_gps = 0; |
|
rfcp->rfc_rfp = rfp; |
|
} |
|
cur_ops->call(&rfcp->rh, rcu_torture_fwd_cb_cr); |
|
rcu_torture_fwd_prog_cond_resched(n_launders + n_max_cbs); |
|
if (tick_nohz_full_enabled()) { |
|
local_irq_save(flags); |
|
rcu_momentary_dyntick_idle(); |
|
local_irq_restore(flags); |
|
} |
|
} |
|
stoppedat = jiffies; |
|
n_launders_cb_snap = READ_ONCE(rfp->n_launders_cb); |
|
cver = READ_ONCE(rcu_torture_current_version) - cver; |
|
gps = rcutorture_seq_diff(cur_ops->get_gp_seq(), gps); |
|
cur_ops->cb_barrier(); /* Wait for callbacks to be invoked. */ |
|
(void)rcu_torture_fwd_prog_cbfree(rfp); |
|
|
|
if (!torture_must_stop() && !READ_ONCE(rcu_fwd_emergency_stop) && |
|
!shutdown_time_arrived()) { |
|
WARN_ON(n_max_gps < MIN_FWD_CBS_LAUNDERED); |
|
pr_alert("%s Duration %lu barrier: %lu pending %ld n_launders: %ld n_launders_sa: %ld n_max_gps: %ld n_max_cbs: %ld cver %ld gps %ld\n", |
|
__func__, |
|
stoppedat - rfp->rcu_fwd_startat, jiffies - stoppedat, |
|
n_launders + n_max_cbs - n_launders_cb_snap, |
|
n_launders, n_launders_sa, |
|
n_max_gps, n_max_cbs, cver, gps); |
|
rcu_torture_fwd_cb_hist(rfp); |
|
} |
|
schedule_timeout_uninterruptible(HZ); /* Let CBs drain. */ |
|
tick_dep_clear_task(current, TICK_DEP_BIT_RCU); |
|
WRITE_ONCE(rcu_fwd_cb_nodelay, false); |
|
} |
|
|
|
|
|
/* |
|
* OOM notifier, but this only prints diagnostic information for the |
|
* current forward-progress test. |
|
*/ |
|
static int rcutorture_oom_notify(struct notifier_block *self, |
|
unsigned long notused, void *nfreed) |
|
{ |
|
struct rcu_fwd *rfp; |
|
|
|
mutex_lock(&rcu_fwd_mutex); |
|
rfp = rcu_fwds; |
|
if (!rfp) { |
|
mutex_unlock(&rcu_fwd_mutex); |
|
return NOTIFY_OK; |
|
} |
|
WARN(1, "%s invoked upon OOM during forward-progress testing.\n", |
|
__func__); |
|
rcu_torture_fwd_cb_hist(rfp); |
|
rcu_fwd_progress_check(1 + (jiffies - READ_ONCE(rfp->rcu_fwd_startat)) / 2); |
|
WRITE_ONCE(rcu_fwd_emergency_stop, true); |
|
smp_mb(); /* Emergency stop before free and wait to avoid hangs. */ |
|
pr_info("%s: Freed %lu RCU callbacks.\n", |
|
__func__, rcu_torture_fwd_prog_cbfree(rfp)); |
|
rcu_barrier(); |
|
pr_info("%s: Freed %lu RCU callbacks.\n", |
|
__func__, rcu_torture_fwd_prog_cbfree(rfp)); |
|
rcu_barrier(); |
|
pr_info("%s: Freed %lu RCU callbacks.\n", |
|
__func__, rcu_torture_fwd_prog_cbfree(rfp)); |
|
smp_mb(); /* Frees before return to avoid redoing OOM. */ |
|
(*(unsigned long *)nfreed)++; /* Forward progress CBs freed! */ |
|
pr_info("%s returning after OOM processing.\n", __func__); |
|
mutex_unlock(&rcu_fwd_mutex); |
|
return NOTIFY_OK; |
|
} |
|
|
|
static struct notifier_block rcutorture_oom_nb = { |
|
.notifier_call = rcutorture_oom_notify |
|
}; |
|
|
|
/* Carry out grace-period forward-progress testing. */ |
|
static int rcu_torture_fwd_prog(void *args) |
|
{ |
|
int oldnice = task_nice(current); |
|
struct rcu_fwd *rfp = args; |
|
int tested = 0; |
|
int tested_tries = 0; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_fwd_progress task started"); |
|
rcu_bind_current_to_nocb(); |
|
if (!IS_ENABLED(CONFIG_SMP) || !IS_ENABLED(CONFIG_RCU_BOOST)) |
|
set_user_nice(current, MAX_NICE); |
|
do { |
|
schedule_timeout_interruptible(fwd_progress_holdoff * HZ); |
|
WRITE_ONCE(rcu_fwd_emergency_stop, false); |
|
if (!IS_ENABLED(CONFIG_TINY_RCU) || |
|
rcu_inkernel_boot_has_ended()) |
|
rcu_torture_fwd_prog_nr(rfp, &tested, &tested_tries); |
|
if (rcu_inkernel_boot_has_ended()) |
|
rcu_torture_fwd_prog_cr(rfp); |
|
|
|
/* Avoid slow periods, better to test when busy. */ |
|
if (stutter_wait("rcu_torture_fwd_prog")) |
|
sched_set_normal(current, oldnice); |
|
} while (!torture_must_stop()); |
|
/* Short runs might not contain a valid forward-progress attempt. */ |
|
WARN_ON(!tested && tested_tries >= 5); |
|
pr_alert("%s: tested %d tested_tries %d\n", __func__, tested, tested_tries); |
|
torture_kthread_stopping("rcu_torture_fwd_prog"); |
|
return 0; |
|
} |
|
|
|
/* If forward-progress checking is requested and feasible, spawn the thread. */ |
|
static int __init rcu_torture_fwd_prog_init(void) |
|
{ |
|
struct rcu_fwd *rfp; |
|
|
|
if (!fwd_progress) |
|
return 0; /* Not requested, so don't do it. */ |
|
if ((!cur_ops->sync && !cur_ops->call) || |
|
!cur_ops->stall_dur || cur_ops->stall_dur() <= 0 || cur_ops == &rcu_busted_ops) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_fwd_prog_init: Disabled, unsupported by RCU flavor under test"); |
|
return 0; |
|
} |
|
if (stall_cpu > 0) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_fwd_prog_init: Disabled, conflicts with CPU-stall testing"); |
|
if (IS_MODULE(CONFIG_RCU_TORTURE_TESTS)) |
|
return -EINVAL; /* In module, can fail back to user. */ |
|
WARN_ON(1); /* Make sure rcutorture notices conflict. */ |
|
return 0; |
|
} |
|
if (fwd_progress_holdoff <= 0) |
|
fwd_progress_holdoff = 1; |
|
if (fwd_progress_div <= 0) |
|
fwd_progress_div = 4; |
|
rfp = kzalloc(sizeof(*rfp), GFP_KERNEL); |
|
if (!rfp) |
|
return -ENOMEM; |
|
spin_lock_init(&rfp->rcu_fwd_lock); |
|
rfp->rcu_fwd_cb_tail = &rfp->rcu_fwd_cb_head; |
|
mutex_lock(&rcu_fwd_mutex); |
|
rcu_fwds = rfp; |
|
mutex_unlock(&rcu_fwd_mutex); |
|
register_oom_notifier(&rcutorture_oom_nb); |
|
return torture_create_kthread(rcu_torture_fwd_prog, rfp, fwd_prog_task); |
|
} |
|
|
|
static void rcu_torture_fwd_prog_cleanup(void) |
|
{ |
|
struct rcu_fwd *rfp; |
|
|
|
torture_stop_kthread(rcu_torture_fwd_prog, fwd_prog_task); |
|
rfp = rcu_fwds; |
|
mutex_lock(&rcu_fwd_mutex); |
|
rcu_fwds = NULL; |
|
mutex_unlock(&rcu_fwd_mutex); |
|
unregister_oom_notifier(&rcutorture_oom_nb); |
|
kfree(rfp); |
|
} |
|
|
|
/* Callback function for RCU barrier testing. */ |
|
static void rcu_torture_barrier_cbf(struct rcu_head *rcu) |
|
{ |
|
atomic_inc(&barrier_cbs_invoked); |
|
} |
|
|
|
/* IPI handler to get callback posted on desired CPU, if online. */ |
|
static void rcu_torture_barrier1cb(void *rcu_void) |
|
{ |
|
struct rcu_head *rhp = rcu_void; |
|
|
|
cur_ops->call(rhp, rcu_torture_barrier_cbf); |
|
} |
|
|
|
/* kthread function to register callbacks used to test RCU barriers. */ |
|
static int rcu_torture_barrier_cbs(void *arg) |
|
{ |
|
long myid = (long)arg; |
|
bool lastphase = false; |
|
bool newphase; |
|
struct rcu_head rcu; |
|
|
|
init_rcu_head_on_stack(&rcu); |
|
VERBOSE_TOROUT_STRING("rcu_torture_barrier_cbs task started"); |
|
set_user_nice(current, MAX_NICE); |
|
do { |
|
wait_event(barrier_cbs_wq[myid], |
|
(newphase = |
|
smp_load_acquire(&barrier_phase)) != lastphase || |
|
torture_must_stop()); |
|
lastphase = newphase; |
|
if (torture_must_stop()) |
|
break; |
|
/* |
|
* The above smp_load_acquire() ensures barrier_phase load |
|
* is ordered before the following ->call(). |
|
*/ |
|
if (smp_call_function_single(myid, rcu_torture_barrier1cb, |
|
&rcu, 1)) { |
|
// IPI failed, so use direct call from current CPU. |
|
cur_ops->call(&rcu, rcu_torture_barrier_cbf); |
|
} |
|
if (atomic_dec_and_test(&barrier_cbs_count)) |
|
wake_up(&barrier_wq); |
|
} while (!torture_must_stop()); |
|
if (cur_ops->cb_barrier != NULL) |
|
cur_ops->cb_barrier(); |
|
destroy_rcu_head_on_stack(&rcu); |
|
torture_kthread_stopping("rcu_torture_barrier_cbs"); |
|
return 0; |
|
} |
|
|
|
/* kthread function to drive and coordinate RCU barrier testing. */ |
|
static int rcu_torture_barrier(void *arg) |
|
{ |
|
int i; |
|
|
|
VERBOSE_TOROUT_STRING("rcu_torture_barrier task starting"); |
|
do { |
|
atomic_set(&barrier_cbs_invoked, 0); |
|
atomic_set(&barrier_cbs_count, n_barrier_cbs); |
|
/* Ensure barrier_phase ordered after prior assignments. */ |
|
smp_store_release(&barrier_phase, !barrier_phase); |
|
for (i = 0; i < n_barrier_cbs; i++) |
|
wake_up(&barrier_cbs_wq[i]); |
|
wait_event(barrier_wq, |
|
atomic_read(&barrier_cbs_count) == 0 || |
|
torture_must_stop()); |
|
if (torture_must_stop()) |
|
break; |
|
n_barrier_attempts++; |
|
cur_ops->cb_barrier(); /* Implies smp_mb() for wait_event(). */ |
|
if (atomic_read(&barrier_cbs_invoked) != n_barrier_cbs) { |
|
n_rcu_torture_barrier_error++; |
|
pr_err("barrier_cbs_invoked = %d, n_barrier_cbs = %d\n", |
|
atomic_read(&barrier_cbs_invoked), |
|
n_barrier_cbs); |
|
WARN_ON(1); |
|
// Wait manually for the remaining callbacks |
|
i = 0; |
|
do { |
|
if (WARN_ON(i++ > HZ)) |
|
i = INT_MIN; |
|
schedule_timeout_interruptible(1); |
|
cur_ops->cb_barrier(); |
|
} while (atomic_read(&barrier_cbs_invoked) != |
|
n_barrier_cbs && |
|
!torture_must_stop()); |
|
smp_mb(); // Can't trust ordering if broken. |
|
if (!torture_must_stop()) |
|
pr_err("Recovered: barrier_cbs_invoked = %d\n", |
|
atomic_read(&barrier_cbs_invoked)); |
|
} else { |
|
n_barrier_successes++; |
|
} |
|
schedule_timeout_interruptible(HZ / 10); |
|
} while (!torture_must_stop()); |
|
torture_kthread_stopping("rcu_torture_barrier"); |
|
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) { |
|
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); |
|
return 0; |
|
} |
|
atomic_set(&barrier_cbs_count, 0); |
|
atomic_set(&barrier_cbs_invoked, 0); |
|
barrier_cbs_tasks = |
|
kcalloc(n_barrier_cbs, sizeof(barrier_cbs_tasks[0]), |
|
GFP_KERNEL); |
|
barrier_cbs_wq = |
|
kcalloc(n_barrier_cbs, sizeof(barrier_cbs_wq[0]), GFP_KERNEL); |
|
if (barrier_cbs_tasks == NULL || !barrier_cbs_wq) |
|
return -ENOMEM; |
|
for (i = 0; i < n_barrier_cbs; i++) { |
|
init_waitqueue_head(&barrier_cbs_wq[i]); |
|
ret = torture_create_kthread(rcu_torture_barrier_cbs, |
|
(void *)(long)i, |
|
barrier_cbs_tasks[i]); |
|
if (ret) |
|
return ret; |
|
} |
|
return torture_create_kthread(rcu_torture_barrier, NULL, barrier_task); |
|
} |
|
|
|
/* Clean up after RCU barrier testing. */ |
|
static void rcu_torture_barrier_cleanup(void) |
|
{ |
|
int i; |
|
|
|
torture_stop_kthread(rcu_torture_barrier, barrier_task); |
|
if (barrier_cbs_tasks != NULL) { |
|
for (i = 0; i < n_barrier_cbs; i++) |
|
torture_stop_kthread(rcu_torture_barrier_cbs, |
|
barrier_cbs_tasks[i]); |
|
kfree(barrier_cbs_tasks); |
|
barrier_cbs_tasks = NULL; |
|
} |
|
if (barrier_cbs_wq != NULL) { |
|
kfree(barrier_cbs_wq); |
|
barrier_cbs_wq = NULL; |
|
} |
|
} |
|
|
|
static bool rcu_torture_can_boost(void) |
|
{ |
|
static int boost_warn_once; |
|
int prio; |
|
|
|
if (!(test_boost == 1 && cur_ops->can_boost) && test_boost != 2) |
|
return false; |
|
if (!cur_ops->start_gp_poll || !cur_ops->poll_gp_state) |
|
return false; |
|
|
|
prio = rcu_get_gp_kthreads_prio(); |
|
if (!prio) |
|
return false; |
|
|
|
if (prio < 2) { |
|
if (boost_warn_once == 1) |
|
return false; |
|
|
|
pr_alert("%s: WARN: RCU kthread priority too low to test boosting. Skipping RCU boost test. Try passing rcutree.kthread_prio > 1 on the kernel command line.\n", KBUILD_MODNAME); |
|
boost_warn_once = 1; |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
static bool read_exit_child_stop; |
|
static bool read_exit_child_stopped; |
|
static wait_queue_head_t read_exit_wq; |
|
|
|
// Child kthread which just does an rcutorture reader and exits. |
|
static int rcu_torture_read_exit_child(void *trsp_in) |
|
{ |
|
struct torture_random_state *trsp = trsp_in; |
|
|
|
set_user_nice(current, MAX_NICE); |
|
// Minimize time between reading and exiting. |
|
while (!kthread_should_stop()) |
|
schedule_timeout_uninterruptible(1); |
|
(void)rcu_torture_one_read(trsp, -1); |
|
return 0; |
|
} |
|
|
|
// Parent kthread which creates and destroys read-exit child kthreads. |
|
static int rcu_torture_read_exit(void *unused) |
|
{ |
|
int count = 0; |
|
bool errexit = false; |
|
int i; |
|
struct task_struct *tsp; |
|
DEFINE_TORTURE_RANDOM(trs); |
|
|
|
// Allocate and initialize. |
|
set_user_nice(current, MAX_NICE); |
|
VERBOSE_TOROUT_STRING("rcu_torture_read_exit: Start of test"); |
|
|
|
// Each pass through this loop does one read-exit episode. |
|
do { |
|
if (++count > read_exit_burst) { |
|
VERBOSE_TOROUT_STRING("rcu_torture_read_exit: End of episode"); |
|
rcu_barrier(); // Wait for task_struct free, avoid OOM. |
|
for (i = 0; i < read_exit_delay; i++) { |
|
schedule_timeout_uninterruptible(HZ); |
|
if (READ_ONCE(read_exit_child_stop)) |
|
break; |
|
} |
|
if (!READ_ONCE(read_exit_child_stop)) |
|
VERBOSE_TOROUT_STRING("rcu_torture_read_exit: Start of episode"); |
|
count = 0; |
|
} |
|
if (READ_ONCE(read_exit_child_stop)) |
|
break; |
|
// Spawn child. |
|
tsp = kthread_run(rcu_torture_read_exit_child, |
|
&trs, "%s", |
|
"rcu_torture_read_exit_child"); |
|
if (IS_ERR(tsp)) { |
|
VERBOSE_TOROUT_ERRSTRING("out of memory"); |
|
errexit = true; |
|
tsp = NULL; |
|
break; |
|
} |
|
cond_resched(); |
|
kthread_stop(tsp); |
|
n_read_exits ++; |
|
stutter_wait("rcu_torture_read_exit"); |
|
} while (!errexit && !READ_ONCE(read_exit_child_stop)); |
|
|
|
// Clean up and exit. |
|
smp_store_release(&read_exit_child_stopped, true); // After reaping. |
|
smp_mb(); // Store before wakeup. |
|
wake_up(&read_exit_wq); |
|
while (!torture_must_stop()) |
|
schedule_timeout_uninterruptible(1); |
|
torture_kthread_stopping("rcu_torture_read_exit"); |
|
return 0; |
|
} |
|
|
|
static int rcu_torture_read_exit_init(void) |
|
{ |
|
if (read_exit_burst <= 0) |
|
return -EINVAL; |
|
init_waitqueue_head(&read_exit_wq); |
|
read_exit_child_stop = false; |
|
read_exit_child_stopped = false; |
|
return torture_create_kthread(rcu_torture_read_exit, NULL, |
|
read_exit_task); |
|
} |
|
|
|
static void rcu_torture_read_exit_cleanup(void) |
|
{ |
|
if (!read_exit_task) |
|
return; |
|
WRITE_ONCE(read_exit_child_stop, true); |
|
smp_mb(); // Above write before wait. |
|
wait_event(read_exit_wq, smp_load_acquire(&read_exit_child_stopped)); |
|
torture_stop_kthread(rcutorture_read_exit, read_exit_task); |
|
} |
|
|
|
static enum cpuhp_state rcutor_hp; |
|
|
|
static void |
|
rcu_torture_cleanup(void) |
|
{ |
|
int firsttime; |
|
int flags = 0; |
|
unsigned long gp_seq = 0; |
|
int i; |
|
|
|
if (torture_cleanup_begin()) { |
|
if (cur_ops->cb_barrier != NULL) |
|
cur_ops->cb_barrier(); |
|
return; |
|
} |
|
if (!cur_ops) { |
|
torture_cleanup_end(); |
|
return; |
|
} |
|
|
|
if (cur_ops->gp_kthread_dbg) |
|
cur_ops->gp_kthread_dbg(); |
|
rcu_torture_read_exit_cleanup(); |
|
rcu_torture_barrier_cleanup(); |
|
rcu_torture_fwd_prog_cleanup(); |
|
torture_stop_kthread(rcu_torture_stall, stall_task); |
|
torture_stop_kthread(rcu_torture_writer, writer_task); |
|
|
|
if (nocb_tasks) { |
|
for (i = 0; i < nrealnocbers; i++) |
|
torture_stop_kthread(rcu_nocb_toggle, nocb_tasks[i]); |
|
kfree(nocb_tasks); |
|
nocb_tasks = NULL; |
|
} |
|
|
|
if (reader_tasks) { |
|
for (i = 0; i < nrealreaders; i++) |
|
torture_stop_kthread(rcu_torture_reader, |
|
reader_tasks[i]); |
|
kfree(reader_tasks); |
|
reader_tasks = NULL; |
|
} |
|
kfree(rcu_torture_reader_mbchk); |
|
rcu_torture_reader_mbchk = NULL; |
|
|
|
if (fakewriter_tasks) { |
|
for (i = 0; i < nfakewriters; i++) |
|
torture_stop_kthread(rcu_torture_fakewriter, |
|
fakewriter_tasks[i]); |
|
kfree(fakewriter_tasks); |
|
fakewriter_tasks = NULL; |
|
} |
|
|
|
rcutorture_get_gp_data(cur_ops->ttype, &flags, &gp_seq); |
|
srcutorture_get_gp_data(cur_ops->ttype, srcu_ctlp, &flags, &gp_seq); |
|
pr_alert("%s: End-test grace-period state: g%ld f%#x total-gps=%ld\n", |
|
cur_ops->name, (long)gp_seq, flags, |
|
rcutorture_seq_diff(gp_seq, start_gp_seq)); |
|
torture_stop_kthread(rcu_torture_stats, stats_task); |
|
torture_stop_kthread(rcu_torture_fqs, fqs_task); |
|
if (rcu_torture_can_boost()) |
|
cpuhp_remove_state(rcutor_hp); |
|
|
|
/* |
|
* Wait for all RCU callbacks to fire, then do torture-type-specific |
|
* cleanup operations. |
|
*/ |
|
if (cur_ops->cb_barrier != NULL) |
|
cur_ops->cb_barrier(); |
|
if (cur_ops->cleanup != NULL) |
|
cur_ops->cleanup(); |
|
|
|
rcu_torture_mem_dump_obj(); |
|
|
|
rcu_torture_stats_print(); /* -After- the stats thread is stopped! */ |
|
|
|
if (err_segs_recorded) { |
|
pr_alert("Failure/close-call rcutorture reader segments:\n"); |
|
if (rt_read_nsegs == 0) |
|
pr_alert("\t: No segments recorded!!!\n"); |
|
firsttime = 1; |
|
for (i = 0; i < rt_read_nsegs; i++) { |
|
pr_alert("\t%d: %#x ", i, err_segs[i].rt_readstate); |
|
if (err_segs[i].rt_delay_jiffies != 0) { |
|
pr_cont("%s%ldjiffies", firsttime ? "" : "+", |
|
err_segs[i].rt_delay_jiffies); |
|
firsttime = 0; |
|
} |
|
if (err_segs[i].rt_delay_ms != 0) { |
|
pr_cont("%s%ldms", firsttime ? "" : "+", |
|
err_segs[i].rt_delay_ms); |
|
firsttime = 0; |
|
} |
|
if (err_segs[i].rt_delay_us != 0) { |
|
pr_cont("%s%ldus", firsttime ? "" : "+", |
|
err_segs[i].rt_delay_us); |
|
firsttime = 0; |
|
} |
|
pr_cont("%s\n", |
|
err_segs[i].rt_preempted ? "preempted" : ""); |
|
|
|
} |
|
} |
|
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 (torture_onoff_failures()) |
|
rcu_torture_print_module_parms(cur_ops, |
|
"End of test: RCU_HOTPLUG"); |
|
else |
|
rcu_torture_print_module_parms(cur_ops, "End of test: SUCCESS"); |
|
torture_cleanup_end(); |
|
} |
|
|
|
#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("%s: duplicated callback was invoked.\n", KBUILD_MODNAME); |
|
} |
|
#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; |
|
struct rcu_head *rhp = kmalloc(sizeof(*rhp), GFP_KERNEL); |
|
|
|
init_rcu_head_on_stack(&rh1); |
|
init_rcu_head_on_stack(&rh2); |
|
pr_alert("%s: WARN: Duplicate call_rcu() test starting.\n", KBUILD_MODNAME); |
|
|
|
/* 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. */ |
|
if (rhp) { |
|
call_rcu(rhp, rcu_torture_leak_cb); |
|
call_rcu(rhp, rcu_torture_err_cb); /* Another 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("%s: WARN: Duplicate call_rcu() test complete.\n", KBUILD_MODNAME); |
|
destroy_rcu_head_on_stack(&rh1); |
|
destroy_rcu_head_on_stack(&rh2); |
|
#else /* #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD */ |
|
pr_alert("%s: !CONFIG_DEBUG_OBJECTS_RCU_HEAD, not testing duplicate call_rcu()\n", KBUILD_MODNAME); |
|
#endif /* #else #ifdef CONFIG_DEBUG_OBJECTS_RCU_HEAD */ |
|
} |
|
|
|
static void rcutorture_sync(void) |
|
{ |
|
static unsigned long n; |
|
|
|
if (cur_ops->sync && !(++n & 0xfff)) |
|
cur_ops->sync(); |
|
} |
|
|
|
static int __init |
|
rcu_torture_init(void) |
|
{ |
|
long i; |
|
int cpu; |
|
int firsterr = 0; |
|
int flags = 0; |
|
unsigned long gp_seq = 0; |
|
static struct rcu_torture_ops *torture_ops[] = { |
|
&rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, |
|
&busted_srcud_ops, &tasks_ops, &tasks_rude_ops, |
|
&tasks_tracing_ops, &trivial_ops, |
|
}; |
|
|
|
if (!torture_init_begin(torture_type, verbose)) |
|
return -EBUSY; |
|
|
|
/* Process args and tell the world that the torturer is on the job. */ |
|
for (i = 0; i < ARRAY_SIZE(torture_ops); i++) { |
|
cur_ops = torture_ops[i]; |
|
if (strcmp(torture_type, cur_ops->name) == 0) |
|
break; |
|
} |
|
if (i == ARRAY_SIZE(torture_ops)) { |
|
pr_alert("rcu-torture: invalid torture type: \"%s\"\n", |
|
torture_type); |
|
pr_alert("rcu-torture types:"); |
|
for (i = 0; i < ARRAY_SIZE(torture_ops); i++) |
|
pr_cont(" %s", torture_ops[i]->name); |
|
pr_cont("\n"); |
|
firsterr = -EINVAL; |
|
cur_ops = NULL; |
|
goto unwind; |
|
} |
|
if (cur_ops->fqs == NULL && fqs_duration != 0) { |
|
pr_alert("rcu-torture: ->fqs NULL and non-zero fqs_duration, fqs disabled.\n"); |
|
fqs_duration = 0; |
|
} |
|
if (cur_ops->init) |
|
cur_ops->init(); |
|
|
|
if (nreaders >= 0) { |
|
nrealreaders = nreaders; |
|
} else { |
|
nrealreaders = num_online_cpus() - 2 - nreaders; |
|
if (nrealreaders <= 0) |
|
nrealreaders = 1; |
|
} |
|
rcu_torture_print_module_parms(cur_ops, "Start of test"); |
|
rcutorture_get_gp_data(cur_ops->ttype, &flags, &gp_seq); |
|
srcutorture_get_gp_data(cur_ops->ttype, srcu_ctlp, &flags, &gp_seq); |
|
start_gp_seq = gp_seq; |
|
pr_alert("%s: Start-test grace-period state: g%ld f%#x\n", |
|
cur_ops->name, (long)gp_seq, flags); |
|
|
|
/* Set up the freelist. */ |
|
|
|
INIT_LIST_HEAD(&rcu_torture_freelist); |
|
for (i = 0; i < ARRAY_SIZE(rcu_tortures); i++) { |
|
rcu_tortures[i].rtort_mbtest = 0; |
|
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); |
|
atomic_set(&n_rcu_torture_mberror, 0); |
|
atomic_set(&n_rcu_torture_mbchk_fail, 0); |
|
atomic_set(&n_rcu_torture_mbchk_tries, 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; |
|
n_rcu_torture_boosts = 0; |
|
for (i = 0; i < RCU_TORTURE_PIPE_LEN + 1; i++) |
|
atomic_set(&rcu_torture_wcount[i], 0); |
|
for_each_possible_cpu(cpu) { |
|
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; |
|
} |
|
} |
|
err_segs_recorded = 0; |
|
rt_read_nsegs = 0; |
|
|
|
/* Start up the kthreads. */ |
|
|
|
rcu_torture_write_types(); |
|
firsterr = torture_create_kthread(rcu_torture_writer, NULL, |
|
writer_task); |
|
if (firsterr) |
|
goto unwind; |
|
if (nfakewriters > 0) { |
|
fakewriter_tasks = kcalloc(nfakewriters, |
|
sizeof(fakewriter_tasks[0]), |
|
GFP_KERNEL); |
|
if (fakewriter_tasks == NULL) { |
|
VERBOSE_TOROUT_ERRSTRING("out of memory"); |
|
firsterr = -ENOMEM; |
|
goto unwind; |
|
} |
|
} |
|
for (i = 0; i < nfakewriters; i++) { |
|
firsterr = torture_create_kthread(rcu_torture_fakewriter, |
|
NULL, fakewriter_tasks[i]); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
reader_tasks = kcalloc(nrealreaders, sizeof(reader_tasks[0]), |
|
GFP_KERNEL); |
|
rcu_torture_reader_mbchk = kcalloc(nrealreaders, sizeof(*rcu_torture_reader_mbchk), |
|
GFP_KERNEL); |
|
if (!reader_tasks || !rcu_torture_reader_mbchk) { |
|
VERBOSE_TOROUT_ERRSTRING("out of memory"); |
|
firsterr = -ENOMEM; |
|
goto unwind; |
|
} |
|
for (i = 0; i < nrealreaders; i++) { |
|
rcu_torture_reader_mbchk[i].rtc_chkrdr = -1; |
|
firsterr = torture_create_kthread(rcu_torture_reader, (void *)i, |
|
reader_tasks[i]); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
nrealnocbers = nocbs_nthreads; |
|
if (WARN_ON(nrealnocbers < 0)) |
|
nrealnocbers = 1; |
|
if (WARN_ON(nocbs_toggle < 0)) |
|
nocbs_toggle = HZ; |
|
if (nrealnocbers > 0) { |
|
nocb_tasks = kcalloc(nrealnocbers, sizeof(nocb_tasks[0]), GFP_KERNEL); |
|
if (nocb_tasks == NULL) { |
|
VERBOSE_TOROUT_ERRSTRING("out of memory"); |
|
firsterr = -ENOMEM; |
|
goto unwind; |
|
} |
|
} else { |
|
nocb_tasks = NULL; |
|
} |
|
for (i = 0; i < nrealnocbers; i++) { |
|
firsterr = torture_create_kthread(rcu_nocb_toggle, NULL, nocb_tasks[i]); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
if (stat_interval > 0) { |
|
firsterr = torture_create_kthread(rcu_torture_stats, NULL, |
|
stats_task); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
if (test_no_idle_hz && shuffle_interval > 0) { |
|
firsterr = torture_shuffle_init(shuffle_interval * HZ); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
if (stutter < 0) |
|
stutter = 0; |
|
if (stutter) { |
|
int t; |
|
|
|
t = cur_ops->stall_dur ? cur_ops->stall_dur() : stutter * HZ; |
|
firsterr = torture_stutter_init(stutter * HZ, t); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
if (fqs_duration < 0) |
|
fqs_duration = 0; |
|
if (fqs_duration) { |
|
/* Create the fqs thread */ |
|
firsterr = torture_create_kthread(rcu_torture_fqs, NULL, |
|
fqs_task); |
|
if (firsterr) |
|
goto unwind; |
|
} |
|
if (test_boost_interval < 1) |
|
test_boost_interval = 1; |
|
if (test_boost_duration < 2) |
|
test_boost_duration = 2; |
|
if (rcu_torture_can_boost()) { |
|
|
|
boost_starttime = jiffies + test_boost_interval * HZ; |
|
|
|
firsterr = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "RCU_TORTURE", |
|
rcutorture_booster_init, |
|
rcutorture_booster_cleanup); |
|
if (firsterr < 0) |
|
goto unwind; |
|
rcutor_hp = firsterr; |
|
|
|
// Testing RCU priority boosting requires rcutorture do |
|
// some serious abuse. Counter this by running ksoftirqd |
|
// at higher priority. |
|
if (IS_BUILTIN(CONFIG_RCU_TORTURE_TEST)) { |
|
for_each_online_cpu(cpu) { |
|
struct sched_param sp; |
|
struct task_struct *t; |
|
|
|
t = per_cpu(ksoftirqd, cpu); |
|
WARN_ON_ONCE(!t); |
|
sp.sched_priority = 2; |
|
sched_setscheduler_nocheck(t, SCHED_FIFO, &sp); |
|
} |
|
} |
|
} |
|
shutdown_jiffies = jiffies + shutdown_secs * HZ; |
|
firsterr = torture_shutdown_init(shutdown_secs, rcu_torture_cleanup); |
|
if (firsterr) |
|
goto unwind; |
|
firsterr = torture_onoff_init(onoff_holdoff * HZ, onoff_interval, |
|
rcutorture_sync); |
|
if (firsterr) |
|
goto unwind; |
|
firsterr = rcu_torture_stall_init(); |
|
if (firsterr) |
|
goto unwind; |
|
firsterr = rcu_torture_fwd_prog_init(); |
|
if (firsterr) |
|
goto unwind; |
|
firsterr = rcu_torture_barrier_init(); |
|
if (firsterr) |
|
goto unwind; |
|
firsterr = rcu_torture_read_exit_init(); |
|
if (firsterr) |
|
goto unwind; |
|
if (object_debug) |
|
rcu_test_debug_objects(); |
|
torture_init_end(); |
|
return 0; |
|
|
|
unwind: |
|
torture_init_end(); |
|
rcu_torture_cleanup(); |
|
if (shutdown_secs) { |
|
WARN_ON(!IS_MODULE(CONFIG_RCU_TORTURE_TEST)); |
|
kernel_power_off(); |
|
} |
|
return firsterr; |
|
} |
|
|
|
module_init(rcu_torture_init); |
|
module_exit(rcu_torture_cleanup);
|
|
|