| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Read-Copy Update module-based torture test facility | |
| * | |
| * Copyright (C) IBM Corporation, 2005, 2006 | |
| * | |
| * Authors: Paul E. McKenney <paulmck@linux.ibm.com> | |
| * Josh Triplett <josh@joshtriplett.org> | |
| * | |
| * 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/rcu_notifier.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 <linux/nmi.h> | |
| #include "rcu.h" | |
| MODULE_DESCRIPTION("Read-Copy Update module-based torture test facility"); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_AUTHOR("Paul E. McKenney <paulmck@linux.ibm.com> and Josh Triplett <josh@joshtriplett.org>"); | |
| // Bits for ->extendables field, extendables param, and related definitions. | |
| #define RCUTORTURE_RDR_SHIFT_1 8 // Put SRCU index in upper bits. | |
| #define RCUTORTURE_RDR_MASK_1 (0xff << RCUTORTURE_RDR_SHIFT_1) | |
| #define RCUTORTURE_RDR_SHIFT_2 16 // Put SRCU index in upper bits. | |
| #define RCUTORTURE_RDR_MASK_2 (0xff << RCUTORTURE_RDR_SHIFT_2) | |
| #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_1 0x20 // ... entering another RCU reader. | |
| #define RCUTORTURE_RDR_RCU_2 0x40 // ... entering another RCU reader. | |
| #define RCUTORTURE_RDR_UPDOWN 0x80 // ... up-read from task, down-read from timer. | |
| // Note: Manual start, automatic end. | |
| #define RCUTORTURE_RDR_NBITS 8 // Number of bits defined above. | |
| #define RCUTORTURE_MAX_EXTEND \ | |
| (RCUTORTURE_RDR_BH | RCUTORTURE_RDR_IRQ | RCUTORTURE_RDR_PREEMPT | \ | |
| RCUTORTURE_RDR_RBH | RCUTORTURE_RDR_SCHED) // Intentionally omit RCUTORTURE_RDR_UPDOWN. | |
| #define RCUTORTURE_RDR_ALLBITS \ | |
| (RCUTORTURE_MAX_EXTEND | RCUTORTURE_RDR_RCU_1 | RCUTORTURE_RDR_RCU_2 | \ | |
| RCUTORTURE_RDR_MASK_1 | RCUTORTURE_RDR_MASK_2) | |
| #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(bool, deboost_timeliness_check, 0, "Enable checks for immediate deboosting"); | |
| 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(int, fwd_progress, 1, "Number of grace-period forward progress tasks (0 to disable)"); | |
| 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_cond_exp, false, "Use conditional/async expedited GP wait primitives"); | |
| torture_param(bool, gp_cond_full, false, "Use conditional/async full-state GP wait primitives"); | |
| torture_param(bool, gp_cond_exp_full, false, | |
| "Use conditional/async full-stateexpedited GP wait primitives"); | |
| torture_param(int, gp_cond_wi, 16 * USEC_PER_SEC / HZ, | |
| "Wait interval for normal conditional grace periods, us (default 16 jiffies)"); | |
| torture_param(int, gp_cond_wi_exp, 128, | |
| "Wait interval for expedited conditional grace periods, us (default 128 us)"); | |
| 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_poll_exp, false, "Use polling expedited GP wait primitives"); | |
| torture_param(bool, gp_poll_full, false, "Use polling full-state GP wait primitives"); | |
| torture_param(bool, gp_poll_exp_full, false, "Use polling full-state expedited GP wait primitives"); | |
| torture_param(int, gp_poll_wi, 16 * USEC_PER_SEC / HZ, | |
| "Wait interval for normal polled grace periods, us (default 16 jiffies)"); | |
| torture_param(int, gp_poll_wi_exp, 128, | |
| "Wait interval for expedited polled grace periods, us (default 128 us)"); | |
| 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, n_up_down, 32, "# of concurrent up/down hrtimer-based RCU readers"); | |
| torture_param(int, nfakewriters, 4, "Number of RCU fake writer threads"); | |
| torture_param(int, nreaders, -1, "Number of RCU reader threads"); | |
| torture_param(bool, nwriters, 1, "Number of RCU writer threads (0 or 1)"); | |
| 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(bool, gpwrap_lag, true, "Enable grace-period wrap lag testing"); | |
| torture_param(int, gpwrap_lag_gps, 8, "Value to set for set_gpwrap_lag during an active testing period."); | |
| torture_param(int, gpwrap_lag_cycle_mins, 30, "Total cycle duration for gpwrap lag testing (in minutes)"); | |
| torture_param(int, gpwrap_lag_active_mins, 5, "Duration for which gpwrap lag is active within each cycle (in minutes)"); | |
| 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, preempt_duration, 0, "Preemption duration (ms), zero to disable"); | |
| torture_param(int, preempt_interval, MSEC_PER_SEC, "Interval between preemptions (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, reader_flavor, SRCU_READ_FLAVOR_NORMAL, "Reader flavors to use, one per bit."); | |
| 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(bool, stall_no_softlockup, false, "Avoid softlockup warning during cpu stall."); | |
| 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_cpu_repeat, 0, "Number of additional stalls after the first one."); | |
| torture_param(int, stall_gp_kthread, 0, "Grace-period kthread stall duration (s)."); | |
| torture_param(bool, stall_only, 0, "Suppress all non-CPU-stall kthreads."); | |
| 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_holdoff, 0, "Holdoff time from rcutorture start, seconds."); | |
| torture_param(int, test_boost_interval, 7, "Interval between boost tests, seconds."); | |
| torture_param(int, test_nmis, 0, "End-test NMI tests, 0 to disable."); | |
| torture_param(bool, test_no_idle_hz, true, "Test support for tickless idle CPUs"); | |
| torture_param(int, test_srcu_lockdep, 0, "Test specified SRCU deadlock scenario."); | |
| 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 int nrealfakewriters; | |
| static struct task_struct *writer_task; | |
| static struct task_struct **fakewriter_tasks; | |
| static struct task_struct **reader_tasks; | |
| static struct task_struct *updown_task; | |
| 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_tasks; | |
| static struct task_struct **barrier_cbs_tasks; | |
| static struct task_struct *barrier_task; | |
| static struct task_struct *read_exit_task; | |
| static struct task_struct *preempt_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_failure; | |
| static long n_rcu_torture_boosts; | |
| static atomic_long_t n_rcu_torture_timers; | |
| static atomic_long_t n_rcu_torture_irqs; | |
| 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_GET_FULL 6 | |
| #define RTWS_COND_GET_EXP 7 | |
| #define RTWS_COND_GET_EXP_FULL 8 | |
| #define RTWS_COND_SYNC 9 | |
| #define RTWS_COND_SYNC_FULL 10 | |
| #define RTWS_COND_SYNC_EXP 11 | |
| #define RTWS_COND_SYNC_EXP_FULL 12 | |
| #define RTWS_POLL_GET 13 | |
| #define RTWS_POLL_GET_FULL 14 | |
| #define RTWS_POLL_GET_EXP 15 | |
| #define RTWS_POLL_GET_EXP_FULL 16 | |
| #define RTWS_POLL_WAIT 17 | |
| #define RTWS_POLL_WAIT_FULL 18 | |
| #define RTWS_POLL_WAIT_EXP 19 | |
| #define RTWS_POLL_WAIT_EXP_FULL 20 | |
| #define RTWS_SYNC 21 | |
| #define RTWS_STUTTER 22 | |
| #define RTWS_STOPPING 23 | |
| 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_GET_FULL", | |
| "RTWS_COND_GET_EXP", | |
| "RTWS_COND_GET_EXP_FULL", | |
| "RTWS_COND_SYNC", | |
| "RTWS_COND_SYNC_FULL", | |
| "RTWS_COND_SYNC_EXP", | |
| "RTWS_COND_SYNC_EXP_FULL", | |
| "RTWS_POLL_GET", | |
| "RTWS_POLL_GET_FULL", | |
| "RTWS_POLL_GET_EXP", | |
| "RTWS_POLL_GET_EXP_FULL", | |
| "RTWS_POLL_WAIT", | |
| "RTWS_POLL_WAIT_FULL", | |
| "RTWS_POLL_WAIT_EXP", | |
| "RTWS_POLL_WAIT_EXP_FULL", | |
| "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; | |
| int rt_cpu; | |
| int rt_end_cpu; | |
| unsigned long long rt_gp_seq; | |
| unsigned long long rt_gp_seq_end; | |
| u64 rt_ts; | |
| }; | |
| static int err_segs_recorded; | |
| static struct rt_read_seg err_segs[RCUTORTURE_RDR_MAX_SEGS]; | |
| static int rt_read_nsegs; | |
| static int rt_read_preempted; | |
| 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 atomic_t 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); // lockdep. | |
| int (*readlock_nesting)(void); // actual nesting, if available, -1 if not. | |
| int (*down_read)(void); | |
| void (*up_read)(int idx); | |
| 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); | |
| void (*exp_current)(void); | |
| unsigned long (*get_gp_state_exp)(void); | |
| unsigned long (*start_gp_poll_exp)(void); | |
| void (*start_gp_poll_exp_full)(struct rcu_gp_seq *gsp); | |
| bool (*poll_gp_state_exp)(unsigned long oldstate); | |
| void (*cond_sync_exp)(unsigned long oldstate); | |
| void (*cond_sync_exp_full)(struct rcu_gp_seq *gsp); | |
| unsigned long (*get_comp_state)(void); | |
| void (*get_comp_state_full)(struct rcu_gp_seq *gsp); | |
| bool (*same_gp_state)(unsigned long oldstate1, unsigned long oldstate2); | |
| bool (*same_gp_state_full)(struct rcu_gp_seq *rgosp1, struct rcu_gp_seq *rgosp2); | |
| unsigned long (*get_gp_state)(void); | |
| void (*get_gp_state_full)(struct rcu_gp_seq *gsp); | |
| unsigned long (*start_gp_poll)(void); | |
| void (*start_gp_poll_full)(struct rcu_gp_seq *gsp); | |
| bool (*poll_gp_state)(unsigned long oldstate); | |
| bool (*poll_gp_state_full)(struct rcu_gp_seq *gsp); | |
| bool (*poll_need_2gp)(bool poll, bool poll_full); | |
| void (*cond_sync)(unsigned long oldstate); | |
| void (*cond_sync_full)(struct rcu_gp_seq *gsp); | |
| int poll_active; | |
| int poll_active_full; | |
| 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); | |
| void (*get_gp_data)(int *flags, unsigned long *gp_seq); | |
| void (*gp_slow_register)(atomic_t *rgssp); | |
| void (*gp_slow_unregister)(atomic_t *rgssp); | |
| bool (*reader_blocked)(void); | |
| unsigned long long (*gather_gp_seqs)(void); | |
| void (*format_gp_seqs)(unsigned long long seqs, char *cp, size_t len); | |
| void (*set_gpwrap_lag)(unsigned long lag); | |
| int (*get_gpwrap_count)(int cpu); | |
| bool (*is_task_rcu_boosted)(void); | |
| long cbflood_max; | |
| int irq_capable; | |
| int can_boost; | |
| int extendables; | |
| int slow_gps; | |
| int no_pi_lock; | |
| int debug_objects; | |
| int start_poll_irqsoff; | |
| int have_up_down; | |
| 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) | |
| { | |
| 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; | |
| // If there is a forward-progress test in flight, don't delay. | |
| if (atomic_read(&rcu_fwd_cb_nodelay)) | |
| return; | |
| // 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 (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) { | |
| started = cur_ops->get_gp_seq(); | |
| ts = rcu_trace_clock_local(); | |
| if ((preempt_count() & HARDIRQ_MASK) || softirq_count()) | |
| 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. */ | |
| } | |
| static void rcu_torture_read_unlock(int idx) | |
| { | |
| rcu_read_unlock(); | |
| } | |
| static int rcu_torture_readlock_nesting(void) | |
| { | |
| if (IS_ENABLED(CONFIG_PREEMPT_RCU)) | |
| return rcu_preempt_depth(); | |
| if (IS_ENABLED(CONFIG_PREEMPT_COUNT)) | |
| return (preempt_count() & PREEMPT_MASK); | |
| return -1; | |
| } | |
| /* | |
| * 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 = 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); | |
| ASSERT_EXCLUSIVE_WRITER(rp->rtort_pipe_count); | |
| if (i + 1 >= 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 bool rcu_poll_need_2gp(bool poll, bool poll_full) | |
| { | |
| return poll; | |
| } | |
| 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, | |
| .readlock_nesting = rcu_torture_readlock_nesting, | |
| .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, | |
| .same_gp_state = same_state_synchronize_rcu, | |
| .same_gp_state_full = same_state_synchronize_rcu_full, | |
| .get_comp_state = get_completed_synchronize_rcu, | |
| .get_comp_state_full = get_completed_synchronize_rcu_full, | |
| .get_gp_state = get_state_synchronize_rcu, | |
| .get_gp_state_full = get_state_synchronize_rcu_full, | |
| .start_gp_poll = start_poll_synchronize_rcu, | |
| .start_gp_poll_full = start_poll_synchronize_rcu_full, | |
| .poll_gp_state = poll_state_synchronize_rcu, | |
| .poll_gp_state_full = poll_state_synchronize_rcu_full, | |
| .poll_need_2gp = rcu_poll_need_2gp, | |
| .cond_sync = cond_synchronize_rcu, | |
| .cond_sync_full = cond_synchronize_rcu_full, | |
| .poll_active = NUM_ACTIVE_RCU_POLL_OLDSTATE, | |
| .poll_active_full = NUM_ACTIVE_RCU_POLL_FULL_OLDSTATE, | |
| .get_gp_state_exp = get_state_synchronize_rcu, | |
| .start_gp_poll_exp = start_poll_synchronize_rcu_expedited, | |
| .start_gp_poll_exp_full = start_poll_synchronize_rcu_expedited_full, | |
| .poll_gp_state_exp = poll_state_synchronize_rcu, | |
| .cond_sync_exp = cond_synchronize_rcu_expedited, | |
| .cond_sync_exp_full = cond_synchronize_rcu_expedited_full, | |
| .call = call_rcu, | |
| .cb_barrier = rcu_barrier, | |
| .fqs = rcu_force_quiescent_state, | |
| .gp_kthread_dbg = show_rcu_gp_kthreads, | |
| .check_boost_failed = rcu_check_boost_fail, | |
| .stall_dur = rcu_jiffies_till_stall_check, | |
| .get_gp_data = rcutorture_get_gp_data, | |
| .gp_slow_register = rcu_gp_slow_register, | |
| .gp_slow_unregister = rcu_gp_slow_unregister, | |
| .reader_blocked = IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU) | |
| ? has_rcu_reader_blocked | |
| : NULL, | |
| .gather_gp_seqs = rcutorture_gather_gp_seqs, | |
| .format_gp_seqs = rcutorture_format_gp_seqs, | |
| .set_gpwrap_lag = rcu_set_gpwrap_lag, | |
| .get_gpwrap_count = rcu_get_gpwrap_count, | |
| .is_task_rcu_boosted = rcu_is_task_rcu_boosted, | |
| .irq_capable = 1, | |
| .can_boost = IS_ENABLED(CONFIG_RCU_BOOST), | |
| .extendables = RCUTORTURE_MAX_EXTEND, | |
| .debug_objects = 1, | |
| .start_poll_irqsoff = 1, | |
| .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, | |
| .gather_gp_seqs = rcutorture_gather_gp_seqs, | |
| .format_gp_seqs = rcutorture_format_gp_seqs, | |
| .irq_capable = 1, | |
| .extendables = RCUTORTURE_MAX_EXTEND, | |
| .name = "busted" | |
| }; | |
| /* | |
| * Definitions for srcu torture testing. | |
| */ | |
| DEFINE_STATIC_SRCU(srcu_ctl); | |
| DEFINE_STATIC_SRCU_FAST(srcu_ctlf); | |
| DEFINE_STATIC_SRCU_FAST_UPDOWN(srcu_ctlfud); | |
| static struct srcu_struct srcu_ctld; | |
| static struct srcu_struct *srcu_ctlp = &srcu_ctl; | |
| static struct rcu_torture_ops srcud_ops; | |
| static void srcu_torture_init(void) | |
| { | |
| rcu_sync_torture_init(); | |
| if (!reader_flavor || (reader_flavor & SRCU_READ_FLAVOR_NORMAL)) | |
| VERBOSE_TOROUT_STRING("srcu_torture_init normal SRCU"); | |
| if (reader_flavor & SRCU_READ_FLAVOR_NMI) | |
| VERBOSE_TOROUT_STRING("srcu_torture_init NMI-safe SRCU"); | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST) { | |
| srcu_ctlp = &srcu_ctlf; | |
| VERBOSE_TOROUT_STRING("srcu_torture_init fast SRCU"); | |
| } | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) { | |
| srcu_ctlp = &srcu_ctlfud; | |
| VERBOSE_TOROUT_STRING("srcu_torture_init fast-up/down SRCU"); | |
| } | |
| } | |
| static void srcu_get_gp_data(int *flags, unsigned long *gp_seq) | |
| { | |
| srcutorture_get_gp_data(srcu_ctlp, flags, gp_seq); | |
| } | |
| static int srcu_torture_read_lock(void) | |
| { | |
| int idx; | |
| struct srcu_ctr __percpu *scp; | |
| int ret = 0; | |
| WARN_ON_ONCE(reader_flavor & ~SRCU_READ_FLAVOR_ALL); | |
| if ((reader_flavor & SRCU_READ_FLAVOR_NORMAL) || !(reader_flavor & SRCU_READ_FLAVOR_ALL)) { | |
| idx = srcu_read_lock(srcu_ctlp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| ret += idx; | |
| } | |
| if (reader_flavor & SRCU_READ_FLAVOR_NMI) { | |
| idx = srcu_read_lock_nmisafe(srcu_ctlp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| ret += idx << 1; | |
| } | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST) { | |
| scp = srcu_read_lock_fast(srcu_ctlp); | |
| idx = __srcu_ptr_to_ctr(srcu_ctlp, scp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| ret += idx << 2; | |
| } | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) { | |
| scp = srcu_read_lock_fast_updown(srcu_ctlp); | |
| idx = __srcu_ptr_to_ctr(srcu_ctlp, scp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| ret += idx << 3; | |
| } | |
| return ret; | |
| } | |
| 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; | |
| // If there is a forward-progress test in flight, don't delay. | |
| if (atomic_read(&rcu_fwd_cb_nodelay)) | |
| return; | |
| // We want there to be long-running readers, but not all the time. | |
| // The !rcu_preempt_depth() is for RCU Tasks Trace. | |
| delay = torture_random(rrsp) % | |
| (nrealreaders * 2 * longdelay * uspertick); | |
| if (!delay && !in_atomic() && !rcu_preempt_depth() && !irqs_disabled()) { | |
| schedule_timeout_interruptible(longdelay); | |
| rtrsp->rt_delay_jiffies = longdelay; | |
| } else { | |
| rcu_read_delay(rrsp, rtrsp); | |
| } | |
| } | |
| static void srcu_torture_read_unlock(int idx) | |
| { | |
| WARN_ON_ONCE((reader_flavor && (idx & ~reader_flavor)) || (!reader_flavor && (idx & ~0x1))); | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) | |
| srcu_read_unlock_fast_updown(srcu_ctlp, | |
| __srcu_ctr_to_ptr(srcu_ctlp, (idx & 0x8) >> 3)); | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST) | |
| srcu_read_unlock_fast(srcu_ctlp, __srcu_ctr_to_ptr(srcu_ctlp, (idx & 0x4) >> 2)); | |
| if (reader_flavor & SRCU_READ_FLAVOR_NMI) | |
| srcu_read_unlock_nmisafe(srcu_ctlp, (idx & 0x2) >> 1); | |
| if ((reader_flavor & SRCU_READ_FLAVOR_NORMAL) || !(reader_flavor & SRCU_READ_FLAVOR_ALL)) | |
| srcu_read_unlock(srcu_ctlp, idx & 0x1); | |
| } | |
| static int torture_srcu_read_lock_held(void) | |
| { | |
| return srcu_read_lock_held(srcu_ctlp); | |
| } | |
| static bool srcu_torture_have_up_down(void) | |
| { | |
| int rf = reader_flavor; | |
| if (!rf) | |
| rf = SRCU_READ_FLAVOR_NORMAL; | |
| return !!(cur_ops->have_up_down & rf); | |
| } | |
| static int srcu_torture_down_read(void) | |
| { | |
| int idx; | |
| struct srcu_ctr __percpu *scp; | |
| WARN_ON_ONCE(reader_flavor & ~SRCU_READ_FLAVOR_ALL); | |
| WARN_ON_ONCE(reader_flavor & (reader_flavor - 1)); | |
| if ((reader_flavor & SRCU_READ_FLAVOR_NORMAL) || !(reader_flavor & SRCU_READ_FLAVOR_ALL)) { | |
| idx = srcu_down_read(srcu_ctlp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| return idx; | |
| } | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) { | |
| scp = srcu_down_read_fast(srcu_ctlp); | |
| idx = __srcu_ptr_to_ctr(srcu_ctlp, scp); | |
| WARN_ON_ONCE(idx & ~0x1); | |
| return idx << 3; | |
| } | |
| WARN_ON_ONCE(1); | |
| return 0; | |
| } | |
| static void srcu_torture_up_read(int idx) | |
| { | |
| WARN_ON_ONCE((reader_flavor && (idx & ~reader_flavor)) || (!reader_flavor && (idx & ~0x1))); | |
| if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) | |
| srcu_up_read_fast(srcu_ctlp, __srcu_ctr_to_ptr(srcu_ctlp, (idx & 0x8) >> 3)); | |
| else if ((reader_flavor & SRCU_READ_FLAVOR_NORMAL) || | |
| !(reader_flavor & SRCU_READ_FLAVOR_ALL)) | |
| srcu_up_read(srcu_ctlp, idx & 0x1); | |
| else | |
| WARN_ON_ONCE(1); | |
| } | |
| static unsigned long srcu_torture_completed(void) | |
| { | |
| return srcu_batches_completed(srcu_ctlp); | |
| } | |
| static void srcu_torture_deferred_free(struct rcu_torture *rp) | |
| { | |
| unsigned long flags; | |
| bool lockit = jiffies & 0x1; | |
| if (lockit) | |
| raw_spin_lock_irqsave(¤t->pi_lock, flags); | |
| call_srcu(srcu_ctlp, &rp->rtort_rcu, rcu_torture_cb); | |
| if (lockit) | |
| raw_spin_unlock_irqrestore(¤t->pi_lock, flags); | |
| } | |
| 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 void srcu_torture_expedite_current(void) | |
| { | |
| srcu_expedite_current(srcu_ctlp); | |
| } | |
| static struct rcu_torture_ops srcu_ops = { | |
| .ttype = SRCU_FLAVOR, | |
| .init = srcu_torture_init, | |
| .readlock = srcu_torture_read_lock, | |
| .read_delay = srcu_read_delay, | |
| .readunlock = srcu_torture_read_unlock, | |
| .down_read = srcu_torture_down_read, | |
| .up_read = srcu_torture_up_read, | |
| .readlock_held = torture_srcu_read_lock_held, | |
| .get_gp_seq = srcu_torture_completed, | |
| .gp_diff = rcu_seq_diff, | |
| .deferred_free = srcu_torture_deferred_free, | |
| .sync = srcu_torture_synchronize, | |
| .exp_sync = srcu_torture_synchronize_expedited, | |
| .exp_current = srcu_torture_expedite_current, | |
| .same_gp_state = same_state_synchronize_srcu, | |
| .get_comp_state = get_completed_synchronize_srcu, | |
| .get_gp_state = srcu_torture_get_gp_state, | |
| .start_gp_poll = srcu_torture_start_gp_poll, | |
| .poll_gp_state = srcu_torture_poll_gp_state, | |
| .poll_active = NUM_ACTIVE_SRCU_POLL_OLDSTATE, | |
| .call = srcu_torture_call, | |
| .cb_barrier = srcu_torture_barrier, | |
| .stats = srcu_torture_stats, | |
| .get_gp_data = srcu_get_gp_data, | |
| .cbflood_max = 50000, | |
| .irq_capable = 1, | |
| .no_pi_lock = IS_ENABLED(CONFIG_TINY_SRCU), | |
| .debug_objects = 1, | |
| .have_up_down = IS_ENABLED(CONFIG_TINY_SRCU) | |
| ? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN, | |
| .name = "srcu" | |
| }; | |
| static void srcud_torture_init(void) | |
| { | |
| rcu_sync_torture_init(); | |
| if (!reader_flavor || (reader_flavor & SRCU_READ_FLAVOR_NORMAL)) { | |
| WARN_ON(init_srcu_struct(&srcu_ctld)); | |
| VERBOSE_TOROUT_STRING("srcud_torture_init normal SRCU"); | |
| } else if (reader_flavor & SRCU_READ_FLAVOR_NMI) { | |
| WARN_ON(init_srcu_struct(&srcu_ctld)); | |
| VERBOSE_TOROUT_STRING("srcud_torture_init NMI-safe SRCU"); | |
| } else if (reader_flavor & SRCU_READ_FLAVOR_FAST) { | |
| WARN_ON(init_srcu_struct_fast(&srcu_ctld)); | |
| VERBOSE_TOROUT_STRING("srcud_torture_init fast SRCU"); | |
| } else if (reader_flavor & SRCU_READ_FLAVOR_FAST_UPDOWN) { | |
| WARN_ON(init_srcu_struct_fast_updown(&srcu_ctld)); | |
| VERBOSE_TOROUT_STRING("srcud_torture_init fast-up/down SRCU"); | |
| } else { | |
| 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 = srcud_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, | |
| .down_read = srcu_torture_down_read, | |
| .up_read = srcu_torture_up_read, | |
| .get_gp_seq = srcu_torture_completed, | |
| .gp_diff = rcu_seq_diff, | |
| .deferred_free = srcu_torture_deferred_free, | |
| .sync = srcu_torture_synchronize, | |
| .exp_sync = srcu_torture_synchronize_expedited, | |
| .exp_current = srcu_torture_expedite_current, | |
| .same_gp_state = same_state_synchronize_srcu, | |
| .get_comp_state = get_completed_synchronize_srcu, | |
| .get_gp_state = srcu_torture_get_gp_state, | |
| .start_gp_poll = srcu_torture_start_gp_poll, | |
| .poll_gp_state = srcu_torture_poll_gp_state, | |
| .poll_active = NUM_ACTIVE_SRCU_POLL_OLDSTATE, | |
| .call = srcu_torture_call, | |
| .cb_barrier = srcu_torture_barrier, | |
| .stats = srcu_torture_stats, | |
| .get_gp_data = srcu_get_gp_data, | |
| .cbflood_max = 50000, | |
| .irq_capable = 1, | |
| .no_pi_lock = IS_ENABLED(CONFIG_TINY_SRCU), | |
| .debug_objects = 1, | |
| .have_up_down = IS_ENABLED(CONFIG_TINY_SRCU) | |
| ? 0 : SRCU_READ_FLAVOR_NORMAL | SRCU_READ_FLAVOR_FAST_UPDOWN, | |
| .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, | |
| .no_pi_lock = IS_ENABLED(CONFIG_TINY_SRCU), | |
| .extendables = RCUTORTURE_MAX_EXTEND, | |
| .name = "busted_srcud" | |
| }; | |
| /* | |
| * Definitions for trivial CONFIG_PREEMPT=n-only torture testing. | |
| * This implementation does not work well with CPU hotplug nor | |
| * with rcutorture's shuffling. | |
| */ | |
| static void synchronize_rcu_trivial(void) | |
| { | |
| int cpu; | |
| for_each_online_cpu(cpu) { | |
| torture_sched_setaffinity(current->pid, cpumask_of(cpu), true); | |
| WARN_ON_ONCE(raw_smp_processor_id() != cpu); | |
| } | |
| } | |
| static void rcu_sync_torture_init_trivial(void) | |
| { | |
| rcu_sync_torture_init(); | |
| // if (onoff_interval || shuffle_interval) { | |
| if (WARN_ONCE(onoff_interval || shuffle_interval, "%s: Non-zero onoff_interval (%d) or shuffle_interval (%d) breaks trivial RCU, resetting to zero", __func__, onoff_interval, shuffle_interval)) { | |
| onoff_interval = 0; | |
| shuffle_interval = 0; | |
| } | |
| } | |
| static int rcu_torture_read_lock_trivial(void) | |
| { | |
| preempt_disable(); | |
| return 0; | |
| } | |
| static void rcu_torture_read_unlock_trivial(int idx) | |
| { | |
| preempt_enable(); | |
| } | |
| static struct rcu_torture_ops trivial_ops = { | |
| .ttype = RCU_TRIVIAL_FLAVOR, | |
| .init = rcu_sync_torture_init_trivial, | |
| .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, | |
| .irq_capable = 1, | |
| .name = "trivial" | |
| }; | |
| #ifdef CONFIG_TRIVIAL_PREEMPT_RCU | |
| /* | |
| * Definitions for trivial CONFIG_PREEMPT=y torture testing. This | |
| * implementation does not work well with large numbers of tasks or with | |
| * long-term preemption. Either or both get you RCU CPU stall warnings. | |
| */ | |
| static void rcu_sync_torture_init_trivial_preempt(void) | |
| { | |
| rcu_sync_torture_init(); | |
| if (WARN_ONCE(onoff_interval || shuffle_interval, "%s: Non-zero onoff_interval (%d) or shuffle_interval (%d) breaks trivial RCU, resetting to zero", __func__, onoff_interval, shuffle_interval)) { | |
| onoff_interval = 0; | |
| shuffle_interval = 0; | |
| } | |
| } | |
| static int rcu_torture_read_lock_trivial_preempt(void) | |
| { | |
| struct task_struct *t = current; | |
| WRITE_ONCE(t->rcu_trivial_preempt_nesting, t->rcu_trivial_preempt_nesting + 1); | |
| smp_mb(); | |
| return 0; | |
| } | |
| static void rcu_torture_read_unlock_trivial_preempt(int idx) | |
| { | |
| struct task_struct *t = current; | |
| smp_store_release(&t->rcu_trivial_preempt_nesting, t->rcu_trivial_preempt_nesting - 1); | |
| } | |
| static struct rcu_torture_ops trivial_preempt_ops = { | |
| .ttype = RCU_TRIVIAL_FLAVOR, | |
| .init = rcu_sync_torture_init_trivial_preempt, | |
| .readlock = rcu_torture_read_lock_trivial_preempt, | |
| .read_delay = rcu_read_delay, // just reuse rcu's version. | |
| .readunlock = rcu_torture_read_unlock_trivial_preempt, | |
| .readlock_held = torture_readlock_not_held, | |
| .get_gp_seq = rcu_no_completed, | |
| .sync = synchronize_rcu_trivial_preempt, | |
| .exp_sync = synchronize_rcu_trivial_preempt, | |
| .irq_capable = 0, // In theory it should be, but let's keep it trivial. | |
| .name = "trivial-preempt" | |
| }; | |
| #define TRIVIAL_PREEMPT_OPS &trivial_preempt_ops, | |
| #else // #ifdef CONFIG_TRIVIAL_PREEMPT_RCU | |
| #define TRIVIAL_PREEMPT_OPS | |
| #endif // #else // #ifdef CONFIG_TRIVIAL_PREEMPT_RCU | |
| #ifdef CONFIG_TASKS_RCU | |
| /* | |
| * 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, | |
| .get_gp_data = rcu_tasks_get_gp_data, | |
| .irq_capable = 1, | |
| .slow_gps = 1, | |
| .name = "tasks" | |
| }; | |
| #define TASKS_OPS &tasks_ops, | |
| #else // #ifdef CONFIG_TASKS_RCU | |
| #define TASKS_OPS | |
| #endif // #else #ifdef CONFIG_TASKS_RCU | |
| #ifdef CONFIG_TASKS_RUDE_RCU | |
| /* | |
| * Definitions for rude RCU-tasks torture testing. | |
| */ | |
| 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, | |
| .sync = synchronize_rcu_tasks_rude, | |
| .exp_sync = synchronize_rcu_tasks_rude, | |
| .gp_kthread_dbg = show_rcu_tasks_rude_gp_kthread, | |
| .get_gp_data = rcu_tasks_rude_get_gp_data, | |
| .cbflood_max = 50000, | |
| .irq_capable = 1, | |
| .name = "tasks-rude" | |
| }; | |
| #define TASKS_RUDE_OPS &tasks_rude_ops, | |
| #else // #ifdef CONFIG_TASKS_RUDE_RCU | |
| #define TASKS_RUDE_OPS | |
| #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU | |
| #ifdef CONFIG_TASKS_TRACE_RCU | |
| /* | |
| * Definitions for tracing RCU-tasks torture testing. | |
| */ | |
| // Note that an RCU Tasks Trace GP must imply an RCU GP. | |
| static int tasks_tracing_torture_read_lock(void) | |
| { | |
| int use_rcu = !(jiffies & 0xff); | |
| if (use_rcu) | |
| rcu_read_lock(); | |
| else | |
| rcu_read_lock_trace(); | |
| return use_rcu; | |
| } | |
| static void tasks_tracing_torture_read_unlock(int use_rcu) | |
| { | |
| if (use_rcu) | |
| rcu_read_unlock(); | |
| else | |
| 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_tasks_trace_batches_completed, | |
| .gp_diff = rcu_seq_diff, | |
| .deferred_free = rcu_tasks_tracing_torture_deferred_free, | |
| .sync = synchronize_rcu_tasks_trace, | |
| .exp_sync = synchronize_rcu_tasks_trace, | |
| .exp_current = rcu_tasks_trace_expedite_current, | |
| .call = call_rcu_tasks_trace, | |
| .cb_barrier = rcu_barrier_tasks_trace, | |
| .cbflood_max = 50000, | |
| .irq_capable = 1, | |
| .slow_gps = 1, | |
| .name = "tasks-tracing" | |
| }; | |
| #define TASKS_TRACING_OPS &tasks_tracing_ops, | |
| #else // #ifdef CONFIG_TASKS_TRACE_RCU | |
| #define TASKS_TRACING_OPS | |
| #endif // #else #ifdef CONFIG_TASKS_TRACE_RCU | |
| 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) { | |
| if (cpu < 0) | |
| pr_info("Boost inversion persisted: QS from all CPUs\n"); | |
| else | |
| 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; | |
| unsigned long booststarttime = get_torture_init_jiffies() + test_boost_holdoff * HZ; | |
| if (test_boost_holdoff <= 0 || time_after(jiffies, booststarttime)) { | |
| VERBOSE_TOROUT_STRING("rcu_torture_boost started"); | |
| } else { | |
| VERBOSE_TOROUT_STRING("rcu_torture_boost started holdoff period"); | |
| while (time_before(jiffies, booststarttime)) { | |
| schedule_timeout_idle(HZ); | |
| if (kthread_should_stop()) | |
| goto cleanup; | |
| } | |
| VERBOSE_TOROUT_STRING("rcu_torture_boost finished holdoff period"); | |
| } | |
| /* 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 = READ_ONCE(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 == READ_ONCE(boost_starttime) && !kthread_should_stop()) { | |
| if (mutex_trylock(&boost_mutex)) { | |
| if (oldstarttime == boost_starttime) { | |
| WRITE_ONCE(boost_starttime, | |
| jiffies + test_boost_interval * HZ); | |
| n_rcu_torture_boosts++; | |
| } | |
| mutex_unlock(&boost_mutex); | |
| break; | |
| } | |
| schedule_timeout_uninterruptible(HZ / 20); | |
| } | |
| /* Go do the stutter. */ | |
| checkwait: if (stutter_wait("rcu_torture_boost")) | |
| sched_set_fifo_low(current); | |
| } while (!torture_must_stop()); | |
| cleanup: | |
| /* Clean up and exit. */ | |
| while (!kthread_should_stop()) { | |
| torture_shutdown_absorb("rcu_torture_boost"); | |
| schedule_timeout_uninterruptible(HZ / 20); | |
| } | |
| 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(HZ / 20); | |
| } | |
| 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. | |
| static int synctype[ARRAY_SIZE(rcu_torture_writer_state_names)] = { }; | |
| static int nsynctypes; | |
| /* | |
| * Determine which grace-period primitives are available. | |
| */ | |
| static void rcu_torture_write_types(void) | |
| { | |
| bool gp_cond1 = gp_cond, gp_cond_exp1 = gp_cond_exp, gp_cond_full1 = gp_cond_full; | |
| bool gp_cond_exp_full1 = gp_cond_exp_full, gp_exp1 = gp_exp, gp_poll_exp1 = gp_poll_exp; | |
| bool gp_poll_exp_full1 = gp_poll_exp_full, gp_normal1 = gp_normal, gp_poll1 = gp_poll; | |
| bool gp_poll_full1 = gp_poll_full, gp_sync1 = gp_sync; | |
| /* Initialize synctype[] array. If none set, take default. */ | |
| if (!gp_cond1 && | |
| !gp_cond_exp1 && | |
| !gp_cond_full1 && | |
| !gp_cond_exp_full1 && | |
| !gp_exp1 && | |
| !gp_poll_exp1 && | |
| !gp_poll_exp_full1 && | |
| !gp_normal1 && | |
| !gp_poll1 && | |
| !gp_poll_full1 && | |
| !gp_sync1) { | |
| gp_cond1 = true; | |
| gp_cond_exp1 = true; | |
| gp_cond_full1 = true; | |
| gp_cond_exp_full1 = true; | |
| gp_exp1 = true; | |
| gp_poll_exp1 = true; | |
| gp_poll_exp_full1 = true; | |
| gp_normal1 = true; | |
| gp_poll1 = true; | |
| gp_poll_full1 = true; | |
| 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_cond_exp1 && cur_ops->get_gp_state_exp && cur_ops->cond_sync_exp) { | |
| synctype[nsynctypes++] = RTWS_COND_GET_EXP; | |
| pr_info("%s: Testing conditional expedited GPs.\n", __func__); | |
| } else if (gp_cond_exp && (!cur_ops->get_gp_state_exp || !cur_ops->cond_sync_exp)) { | |
| pr_alert("%s: gp_cond_exp without primitives.\n", __func__); | |
| } | |
| if (gp_cond_full1 && cur_ops->get_gp_state && cur_ops->cond_sync_full) { | |
| synctype[nsynctypes++] = RTWS_COND_GET_FULL; | |
| pr_info("%s: Testing conditional full-state GPs.\n", __func__); | |
| } else if (gp_cond_full && (!cur_ops->get_gp_state || !cur_ops->cond_sync_full)) { | |
| pr_alert("%s: gp_cond_full without primitives.\n", __func__); | |
| } | |
| if (gp_cond_exp_full1 && cur_ops->get_gp_state_exp && cur_ops->cond_sync_exp_full) { | |
| synctype[nsynctypes++] = RTWS_COND_GET_EXP_FULL; | |
| pr_info("%s: Testing conditional full-state expedited GPs.\n", __func__); | |
| } else if (gp_cond_exp_full && | |
| (!cur_ops->get_gp_state_exp || !cur_ops->cond_sync_exp_full)) { | |
| pr_alert("%s: gp_cond_exp_full 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->get_comp_state && cur_ops->same_gp_state && | |
| 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_poll_full1 && cur_ops->get_comp_state_full && cur_ops->same_gp_state_full | |
| && cur_ops->start_gp_poll_full && cur_ops->poll_gp_state_full) { | |
| synctype[nsynctypes++] = RTWS_POLL_GET_FULL; | |
| pr_info("%s: Testing polling full-state GPs.\n", __func__); | |
| } else if (gp_poll_full && (!cur_ops->start_gp_poll_full || !cur_ops->poll_gp_state_full)) { | |
| pr_alert("%s: gp_poll_full without primitives.\n", __func__); | |
| } | |
| if (gp_poll_exp1 && cur_ops->start_gp_poll_exp && cur_ops->poll_gp_state_exp) { | |
| synctype[nsynctypes++] = RTWS_POLL_GET_EXP; | |
| pr_info("%s: Testing polling expedited GPs.\n", __func__); | |
| } else if (gp_poll_exp && (!cur_ops->start_gp_poll_exp || !cur_ops->poll_gp_state_exp)) { | |
| pr_alert("%s: gp_poll_exp without primitives.\n", __func__); | |
| } | |
| if (gp_poll_exp_full1 && cur_ops->start_gp_poll_exp_full && cur_ops->poll_gp_state_full) { | |
| synctype[nsynctypes++] = RTWS_POLL_GET_EXP_FULL; | |
| pr_info("%s: Testing polling full-state expedited GPs.\n", __func__); | |
| } else if (gp_poll_exp_full && | |
| (!cur_ops->start_gp_poll_exp_full || !cur_ops->poll_gp_state_full)) { | |
| pr_alert("%s: gp_poll_exp_full 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__); | |
| } | |
| pr_alert("%s: Testing %d update types.\n", __func__, nsynctypes); | |
| pr_info("%s: gp_cond_wi %d gp_cond_wi_exp %d gp_poll_wi %d gp_poll_wi_exp %d\n", __func__, gp_cond_wi, gp_cond_wi_exp, gp_poll_wi, gp_poll_wi_exp); | |
| } | |
| /* | |
| * Do the specified rcu_torture_writer() synchronous grace period, | |
| * while also testing out the polled APIs. Note well that the single-CPU | |
| * grace-period optimizations must be accounted for. | |
| */ | |
| static void do_rtws_sync(struct torture_random_state *trsp, void (*sync)(void)) | |
| { | |
| unsigned long cookie; | |
| struct rcu_gp_seq cookie_full; | |
| bool dopoll; | |
| bool dopoll_full; | |
| unsigned long r = torture_random(trsp); | |
| dopoll = cur_ops->get_gp_state && cur_ops->poll_gp_state && !(r & 0x300); | |
| dopoll_full = cur_ops->get_gp_state_full && cur_ops->poll_gp_state_full && !(r & 0xc00); | |
| if (dopoll || dopoll_full) | |
| cpus_read_lock(); | |
| if (dopoll) | |
| cookie = cur_ops->get_gp_state(); | |
| if (dopoll_full) | |
| cur_ops->get_gp_state_full(&cookie_full); | |
| if (cur_ops->poll_need_2gp && cur_ops->poll_need_2gp(dopoll, dopoll_full)) | |
| sync(); | |
| sync(); | |
| WARN_ONCE(dopoll && !cur_ops->poll_gp_state(cookie), | |
| "%s: Cookie check 3 failed %pS() online %*pbl.", | |
| __func__, sync, cpumask_pr_args(cpu_online_mask)); | |
| WARN_ONCE(dopoll_full && !cur_ops->poll_gp_state_full(&cookie_full), | |
| "%s: Cookie check 4 failed %pS() online %*pbl", | |
| __func__, sync, cpumask_pr_args(cpu_online_mask)); | |
| if (dopoll || dopoll_full) | |
| cpus_read_unlock(); | |
| } | |
| /* | |
| * Do an rcu_barrier() to motivate lazy callbacks during a stutter | |
| * pause. Without this, we can get false-positives rtort_pipe_count | |
| * splats. | |
| */ | |
| static void rcu_torture_writer_work(struct work_struct *work) | |
| { | |
| if (cur_ops->cb_barrier) | |
| cur_ops->cb_barrier(); | |
| } | |
| /* | |
| * 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 booting_still = false; | |
| bool can_expedite = !rcu_gp_is_expedited() && !rcu_gp_is_normal(); | |
| unsigned long cookie; | |
| struct rcu_gp_seq cookie_full; | |
| int expediting = 0; | |
| unsigned long gp_snap; | |
| unsigned long gp_snap1; | |
| struct rcu_gp_seq gp_snap_full; | |
| struct rcu_gp_seq gp_snap1_full; | |
| int i; | |
| int idx; | |
| unsigned long j; | |
| struct work_struct lazy_work; | |
| int oldnice = task_nice(current); | |
| struct rcu_gp_seq *rgo = NULL; | |
| int rgo_size = 0; | |
| struct rcu_torture *rp; | |
| struct rcu_torture *old_rp; | |
| static DEFINE_TORTURE_RANDOM(rand); | |
| unsigned long stallsdone = jiffies; | |
| bool stutter_waited; | |
| unsigned long *ulo = NULL; | |
| int ulo_size = 0; | |
| // If a new stall test is added, this must be adjusted. | |
| if (stall_cpu_holdoff + stall_gp_kthread + stall_cpu) | |
| stallsdone += (stall_cpu_holdoff + stall_gp_kthread + stall_cpu + 60) * | |
| HZ * (stall_cpu_repeat + 1); | |
| 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, | |
| "%s: No update-side primitives.\n", __func__)) { | |
| /* | |
| * 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"); | |
| return 0; | |
| } | |
| if (cur_ops->poll_active > 0) { | |
| ulo = kcalloc(cur_ops->poll_active, sizeof(*ulo), GFP_KERNEL); | |
| if (!WARN_ON(!ulo)) | |
| ulo_size = cur_ops->poll_active; | |
| } | |
| if (cur_ops->poll_active_full > 0) { | |
| rgo = kzalloc_objs(*rgo, cur_ops->poll_active_full); | |
| if (!WARN_ON(!rgo)) | |
| rgo_size = cur_ops->poll_active_full; | |
| } | |
| // If the system is still booting, let it finish. | |
| j = jiffies; | |
| while (!torture_must_stop() && !rcu_inkernel_boot_has_ended()) { | |
| booting_still = true; | |
| schedule_timeout_interruptible(HZ); | |
| } | |
| if (booting_still) | |
| pr_alert("%s" TORTURE_FLAG " Waited %lu jiffies for boot to complete.\n", | |
| torture_type, jiffies - j); | |
| if (IS_ENABLED(CONFIG_RCU_LAZY)) | |
| INIT_WORK_ONSTACK(&lazy_work, rcu_torture_writer_work); | |
| 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; | |
| ASSERT_EXCLUSIVE_WRITER(rp->rtort_pipe_count); | |
| 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); | |
| ASSERT_EXCLUSIVE_WRITER(old_rp->rtort_pipe_count); | |
| // Make sure readers block polled grace periods. | |
| if (cur_ops->get_gp_state && cur_ops->poll_gp_state) { | |
| idx = cur_ops->readlock(); | |
| cookie = cur_ops->get_gp_state(); | |
| WARN_ONCE(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()); | |
| if (cur_ops->get_comp_state) { | |
| cookie = cur_ops->get_comp_state(); | |
| WARN_ON_ONCE(!cur_ops->poll_gp_state(cookie)); | |
| } | |
| cur_ops->readunlock(idx); | |
| } | |
| if (cur_ops->get_gp_state_full && cur_ops->poll_gp_state_full) { | |
| idx = cur_ops->readlock(); | |
| cur_ops->get_gp_state_full(&cookie_full); | |
| WARN_ONCE(cur_ops->poll_gp_state_full(&cookie_full), | |
| "%s: Cookie check 5 failed %s(%d) online %*pbl\n", | |
| __func__, | |
| rcu_torture_writer_state_getname(), | |
| rcu_torture_writer_state, | |
| cpumask_pr_args(cpu_online_mask)); | |
| if (cur_ops->get_comp_state_full) { | |
| cur_ops->get_comp_state_full(&cookie_full); | |
| WARN_ON_ONCE(!cur_ops->poll_gp_state_full(&cookie_full)); | |
| } | |
| 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; | |
| do_rtws_sync(&rand, 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_us(torture_random(&rand) % gp_cond_wi, | |
| 1000, &rand); | |
| rcu_torture_writer_state = RTWS_COND_SYNC; | |
| cur_ops->cond_sync(gp_snap); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_COND_GET_EXP: | |
| rcu_torture_writer_state = RTWS_COND_GET_EXP; | |
| gp_snap = cur_ops->get_gp_state_exp(); | |
| torture_hrtimeout_us(torture_random(&rand) % gp_cond_wi_exp, | |
| 1000, &rand); | |
| rcu_torture_writer_state = RTWS_COND_SYNC_EXP; | |
| cur_ops->cond_sync_exp(gp_snap); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_COND_GET_FULL: | |
| rcu_torture_writer_state = RTWS_COND_GET_FULL; | |
| cur_ops->get_gp_state_full(&gp_snap_full); | |
| torture_hrtimeout_us(torture_random(&rand) % gp_cond_wi, | |
| 1000, &rand); | |
| rcu_torture_writer_state = RTWS_COND_SYNC_FULL; | |
| cur_ops->cond_sync_full(&gp_snap_full); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_COND_GET_EXP_FULL: | |
| rcu_torture_writer_state = RTWS_COND_GET_EXP_FULL; | |
| cur_ops->get_gp_state_full(&gp_snap_full); | |
| torture_hrtimeout_us(torture_random(&rand) % gp_cond_wi_exp, | |
| 1000, &rand); | |
| rcu_torture_writer_state = RTWS_COND_SYNC_EXP_FULL; | |
| cur_ops->cond_sync_exp_full(&gp_snap_full); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_POLL_GET: | |
| rcu_torture_writer_state = RTWS_POLL_GET; | |
| for (i = 0; i < ulo_size; i++) | |
| ulo[i] = cur_ops->get_comp_state(); | |
| gp_snap = cur_ops->start_gp_poll(); | |
| rcu_torture_writer_state = RTWS_POLL_WAIT; | |
| if (cur_ops->exp_current && !(torture_random(&rand) & 0xff)) | |
| cur_ops->exp_current(); | |
| while (!cur_ops->poll_gp_state(gp_snap)) { | |
| gp_snap1 = cur_ops->get_gp_state(); | |
| for (i = 0; i < ulo_size; i++) | |
| if (cur_ops->poll_gp_state(ulo[i]) || | |
| cur_ops->same_gp_state(ulo[i], gp_snap1)) { | |
| ulo[i] = gp_snap1; | |
| break; | |
| } | |
| WARN_ON_ONCE(ulo_size > 0 && i >= ulo_size); | |
| torture_hrtimeout_us(torture_random(&rand) % gp_poll_wi, | |
| 1000, &rand); | |
| } | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_POLL_GET_FULL: | |
| rcu_torture_writer_state = RTWS_POLL_GET_FULL; | |
| for (i = 0; i < rgo_size; i++) | |
| cur_ops->get_comp_state_full(&rgo[i]); | |
| cur_ops->start_gp_poll_full(&gp_snap_full); | |
| rcu_torture_writer_state = RTWS_POLL_WAIT_FULL; | |
| if (cur_ops->exp_current && !(torture_random(&rand) & 0xff)) | |
| cur_ops->exp_current(); | |
| while (!cur_ops->poll_gp_state_full(&gp_snap_full)) { | |
| cur_ops->get_gp_state_full(&gp_snap1_full); | |
| for (i = 0; i < rgo_size; i++) | |
| if (cur_ops->poll_gp_state_full(&rgo[i]) || | |
| cur_ops->same_gp_state_full(&rgo[i], | |
| &gp_snap1_full)) { | |
| rgo[i] = gp_snap1_full; | |
| break; | |
| } | |
| WARN_ON_ONCE(rgo_size > 0 && i >= rgo_size); | |
| torture_hrtimeout_us(torture_random(&rand) % gp_poll_wi, | |
| 1000, &rand); | |
| } | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_POLL_GET_EXP: | |
| rcu_torture_writer_state = RTWS_POLL_GET_EXP; | |
| gp_snap = cur_ops->start_gp_poll_exp(); | |
| rcu_torture_writer_state = RTWS_POLL_WAIT_EXP; | |
| while (!cur_ops->poll_gp_state_exp(gp_snap)) | |
| torture_hrtimeout_us(torture_random(&rand) % gp_poll_wi_exp, | |
| 1000, &rand); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_POLL_GET_EXP_FULL: | |
| rcu_torture_writer_state = RTWS_POLL_GET_EXP_FULL; | |
| cur_ops->start_gp_poll_exp_full(&gp_snap_full); | |
| rcu_torture_writer_state = RTWS_POLL_WAIT_EXP_FULL; | |
| while (!cur_ops->poll_gp_state_full(&gp_snap_full)) | |
| torture_hrtimeout_us(torture_random(&rand) % gp_poll_wi_exp, | |
| 1000, &rand); | |
| rcu_torture_pipe_update(old_rp); | |
| break; | |
| case RTWS_SYNC: | |
| rcu_torture_writer_state = RTWS_SYNC; | |
| do_rtws_sync(&rand, 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; | |
| if (IS_ENABLED(CONFIG_RCU_LAZY)) | |
| queue_work(system_percpu_wq, &lazy_work); | |
| stutter_waited = stutter_wait("rcu_torture_writer"); | |
| if (stutter_waited && | |
| !atomic_read(&rcu_fwd_cb_nodelay) && | |
| !cur_ops->slow_gps && | |
| !torture_must_stop() && | |
| time_after(jiffies, stallsdone)) | |
| 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]) { | |
| tracing_off(); | |
| if (cur_ops->gp_kthread_dbg) | |
| cur_ops->gp_kthread_dbg(); | |
| WARN(1, "%s: rtort_pipe_count: %d\n", __func__, rcu_tortures[i].rtort_pipe_count); | |
| rcu_ftrace_dump(DUMP_ALL); | |
| break; | |
| } | |
| 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); | |
| if (IS_ENABLED(CONFIG_RCU_LAZY)) { | |
| cancel_work_sync(&lazy_work); | |
| destroy_work_on_stack(&lazy_work); | |
| } | |
| kfree(ulo); | |
| kfree(rgo); | |
| 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; | |
| struct rcu_gp_seq gp_snap_full; | |
| DEFINE_TORTURE_RANDOM(rand); | |
| VERBOSE_TOROUT_STRING("rcu_torture_fakewriter task started"); | |
| set_user_nice(current, MAX_NICE); | |
| if (WARN_ONCE(nsynctypes == 0, | |
| "%s: No update-side primitives.\n", __func__)) { | |
| /* | |
| * No updates primitives, so don't try updating. | |
| * The resulting test won't be testing much, hence the | |
| * above WARN_ONCE(). | |
| */ | |
| torture_kthread_stopping("rcu_torture_fakewriter"); | |
| return 0; | |
| } | |
| do { | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 10, &rand); | |
| if (cur_ops->cb_barrier != NULL && | |
| torture_random(&rand) % (nrealfakewriters * 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_COND_GET_EXP: | |
| gp_snap = cur_ops->get_gp_state_exp(); | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand); | |
| cur_ops->cond_sync_exp(gp_snap); | |
| break; | |
| case RTWS_COND_GET_FULL: | |
| cur_ops->get_gp_state_full(&gp_snap_full); | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand); | |
| cur_ops->cond_sync_full(&gp_snap_full); | |
| break; | |
| case RTWS_COND_GET_EXP_FULL: | |
| cur_ops->get_gp_state_full(&gp_snap_full); | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, &rand); | |
| cur_ops->cond_sync_exp_full(&gp_snap_full); | |
| break; | |
| case RTWS_POLL_GET: | |
| if (cur_ops->start_poll_irqsoff) | |
| local_irq_disable(); | |
| gp_snap = cur_ops->start_gp_poll(); | |
| if (cur_ops->start_poll_irqsoff) | |
| local_irq_enable(); | |
| while (!cur_ops->poll_gp_state(gp_snap)) { | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, | |
| &rand); | |
| } | |
| break; | |
| case RTWS_POLL_GET_FULL: | |
| if (cur_ops->start_poll_irqsoff) | |
| local_irq_disable(); | |
| cur_ops->start_gp_poll_full(&gp_snap_full); | |
| if (cur_ops->start_poll_irqsoff) | |
| local_irq_enable(); | |
| while (!cur_ops->poll_gp_state_full(&gp_snap_full)) { | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, | |
| &rand); | |
| } | |
| break; | |
| case RTWS_POLL_GET_EXP: | |
| gp_snap = cur_ops->start_gp_poll_exp(); | |
| while (!cur_ops->poll_gp_state_exp(gp_snap)) { | |
| torture_hrtimeout_jiffies(torture_random(&rand) % 16, | |
| &rand); | |
| } | |
| break; | |
| case RTWS_POLL_GET_EXP_FULL: | |
| cur_ops->start_gp_poll_exp_full(&gp_snap_full); | |
| while (!cur_ops->poll_gp_state_full(&gp_snap_full)) { | |
| 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. | |
| } | |
| static DEFINE_PER_CPU(bool, torture_in_scf_handler); | |
| // Verify the specified RCUTORTURE_RDR* state. | |
| #define ROEC_ARGS "%s %s: Current %#x To add %#x To remove %#x preempt_count() %#x\n", __func__, s, curstate, new, old, preempt_count() | |
| static void rcutorture_one_extend_check(char *s, int curstate, int new, int old) | |
| { | |
| int mask; | |
| if (!IS_ENABLED(CONFIG_RCU_TORTURE_TEST_CHK_RDR_STATE) || in_nmi()) | |
| return; | |
| WARN_ONCE(!(curstate & RCUTORTURE_RDR_IRQ) && irqs_disabled() && !in_hardirq() && !this_cpu_read(torture_in_scf_handler), ROEC_ARGS); | |
| WARN_ONCE((curstate & RCUTORTURE_RDR_IRQ) && !irqs_disabled(), ROEC_ARGS); | |
| // If CONFIG_PREEMPT_COUNT=n, further checks are unreliable. | |
| if (!IS_ENABLED(CONFIG_PREEMPT_COUNT)) | |
| return; | |
| WARN_ONCE((curstate & (RCUTORTURE_RDR_BH | RCUTORTURE_RDR_RBH)) && | |
| !softirq_count(), ROEC_ARGS); | |
| WARN_ONCE((curstate & (RCUTORTURE_RDR_PREEMPT | RCUTORTURE_RDR_SCHED)) && | |
| !(preempt_count() & PREEMPT_MASK), ROEC_ARGS); | |
| WARN_ONCE(cur_ops->readlock_nesting && | |
| (curstate & (RCUTORTURE_RDR_RCU_1 | RCUTORTURE_RDR_RCU_2)) && | |
| cur_ops->readlock_nesting() == 0, ROEC_ARGS); | |
| // Interrupt handlers have all sorts of stuff disabled, so ignore | |
| // unintended disabling. | |
| if (in_serving_softirq() || in_hardirq() || this_cpu_read(torture_in_scf_handler)) | |
| return; | |
| WARN_ONCE(cur_ops->extendables && | |
| !(curstate & (RCUTORTURE_RDR_BH | RCUTORTURE_RDR_RBH)) && | |
| softirq_count(), ROEC_ARGS); | |
| /* | |
| * non-preemptible RCU in a preemptible kernel uses preempt_disable() | |
| * as rcu_read_lock(). | |
| */ | |
| mask = RCUTORTURE_RDR_PREEMPT | RCUTORTURE_RDR_SCHED; | |
| if (!IS_ENABLED(CONFIG_PREEMPT_RCU)) | |
| mask |= RCUTORTURE_RDR_RCU_1 | RCUTORTURE_RDR_RCU_2; | |
| WARN_ONCE(cur_ops->extendables && !(curstate & mask) && | |
| (preempt_count() & PREEMPT_MASK), ROEC_ARGS); | |
| /* | |
| * non-preemptible RCU in a preemptible kernel uses "preempt_count() & | |
| * PREEMPT_MASK" as ->readlock_nesting(). | |
| */ | |
| mask = RCUTORTURE_RDR_RCU_1 | RCUTORTURE_RDR_RCU_2; | |
| if (!IS_ENABLED(CONFIG_PREEMPT_RCU)) | |
| mask |= RCUTORTURE_RDR_PREEMPT | RCUTORTURE_RDR_SCHED; | |
| if (IS_ENABLED(CONFIG_PREEMPT_RT) && softirq_count()) | |
| mask |= RCUTORTURE_RDR_BH | RCUTORTURE_RDR_RBH; | |
| WARN_ONCE(cur_ops->readlock_nesting && !(curstate & mask) && | |
| cur_ops->readlock_nesting() > 0, ROEC_ARGS); | |
| } | |
| /* | |
| * 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) | |
| { | |
| bool first; | |
| unsigned long flags; | |
| int idxnew1 = -1; | |
| int idxnew2 = -1; | |
| int idxold1 = *readstate; | |
| int idxold2 = idxold1; | |
| int statesnew = ~*readstate & newstate; | |
| int statesold = *readstate & ~newstate; | |
| first = idxold1 == 0; | |
| WARN_ON_ONCE(idxold2 < 0); | |
| WARN_ON_ONCE(idxold2 & ~(RCUTORTURE_RDR_ALLBITS | RCUTORTURE_RDR_UPDOWN)); | |
| rcutorture_one_extend_check("before change", idxold1, statesnew, statesold); | |
| 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_RBH) | |
| rcu_read_lock_bh(); | |
| if (statesnew & RCUTORTURE_RDR_IRQ) | |
| local_irq_disable(); | |
| if (statesnew & RCUTORTURE_RDR_PREEMPT) | |
| preempt_disable(); | |
| if (statesnew & RCUTORTURE_RDR_SCHED) | |
| rcu_read_lock_sched(); | |
| if (statesnew & RCUTORTURE_RDR_RCU_1) | |
| idxnew1 = (cur_ops->readlock() << RCUTORTURE_RDR_SHIFT_1) & RCUTORTURE_RDR_MASK_1; | |
| if (statesnew & RCUTORTURE_RDR_RCU_2) | |
| idxnew2 = (cur_ops->readlock() << RCUTORTURE_RDR_SHIFT_2) & RCUTORTURE_RDR_MASK_2; | |
| // Complain unless both the old and the new protection is in place. | |
| rcutorture_one_extend_check("during change", idxold1 | statesnew, statesnew, statesold); | |
| // Sample CPU under both sets of protections to reduce confusion. | |
| if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) { | |
| int cpu = raw_smp_processor_id(); | |
| rtrsp->rt_cpu = cpu; | |
| if (!first) { | |
| rtrsp[-1].rt_end_cpu = cpu; | |
| if (cur_ops->reader_blocked) | |
| rtrsp[-1].rt_preempted = cur_ops->reader_blocked(); | |
| } | |
| } | |
| // Sample grace-period sequence number, as good a place as any. | |
| if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) && cur_ops->gather_gp_seqs) { | |
| rtrsp->rt_gp_seq = cur_ops->gather_gp_seqs(); | |
| rtrsp->rt_ts = ktime_get_mono_fast_ns(); | |
| if (!first) | |
| rtrsp[-1].rt_gp_seq_end = rtrsp->rt_gp_seq; | |
| } | |
| /* | |
| * Next, remove old protection, in decreasing order of strength | |
| * to avoid unlock paths that aren't safe in the stronger | |
| * context. Namely: BH can not be enabled with disabled interrupts. | |
| * Additionally PREEMPT_RT requires that BH is enabled in preemptible | |
| * context. | |
| */ | |
| if (statesold & RCUTORTURE_RDR_IRQ) | |
| local_irq_enable(); | |
| if (statesold & RCUTORTURE_RDR_PREEMPT) | |
| preempt_enable(); | |
| if (statesold & RCUTORTURE_RDR_SCHED) | |
| rcu_read_unlock_sched(); | |
| if (statesold & RCUTORTURE_RDR_BH) | |
| local_bh_enable(); | |
| if (statesold & RCUTORTURE_RDR_RBH) | |
| rcu_read_unlock_bh(); | |
| if (statesold & RCUTORTURE_RDR_RCU_2) { | |
| cur_ops->readunlock((idxold2 & RCUTORTURE_RDR_MASK_2) >> RCUTORTURE_RDR_SHIFT_2); | |
| WARN_ON_ONCE(idxnew2 != -1); | |
| idxold2 = 0; | |
| } | |
| if (statesold & RCUTORTURE_RDR_RCU_1) { | |
| bool lockit; | |
| lockit = !cur_ops->no_pi_lock && !statesnew && !(torture_random(trsp) & 0xffff); | |
| if (lockit) | |
| raw_spin_lock_irqsave(¤t->pi_lock, flags); | |
| cur_ops->readunlock((idxold1 & RCUTORTURE_RDR_MASK_1) >> RCUTORTURE_RDR_SHIFT_1); | |
| WARN_ON_ONCE(idxnew1 != -1); | |
| idxold1 = 0; | |
| if (lockit) | |
| raw_spin_unlock_irqrestore(¤t->pi_lock, flags); | |
| } | |
| if (statesold & RCUTORTURE_RDR_UPDOWN) { | |
| cur_ops->up_read((idxold1 & RCUTORTURE_RDR_MASK_1) >> RCUTORTURE_RDR_SHIFT_1); | |
| WARN_ON_ONCE(idxnew1 != -1); | |
| idxold1 = 0; | |
| } | |
| /* 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 (idxnew1 == -1) | |
| idxnew1 = idxold1 & RCUTORTURE_RDR_MASK_1; | |
| WARN_ON_ONCE(idxnew1 < 0); | |
| if (idxnew2 == -1) | |
| idxnew2 = idxold2 & RCUTORTURE_RDR_MASK_2; | |
| WARN_ON_ONCE(idxnew2 < 0); | |
| *readstate = idxnew1 | idxnew2 | newstate; | |
| WARN_ON_ONCE(*readstate < 0); | |
| if (WARN_ON_ONCE(*readstate & ~RCUTORTURE_RDR_ALLBITS)) | |
| pr_info("Unexpected readstate value of %#x\n", *readstate); | |
| rcutorture_one_extend_check("after change", *readstate, statesnew, statesold); | |
| } | |
| /* 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_1 | RCUTORTURE_RDR_RCU_2; | |
| 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); | |
| unsigned long randmask2 = randmask1 >> 3; | |
| unsigned long preempts = RCUTORTURE_RDR_PREEMPT | RCUTORTURE_RDR_SCHED; | |
| unsigned long preempts_irq = preempts | RCUTORTURE_RDR_IRQ; | |
| unsigned long bhs = RCUTORTURE_RDR_BH | RCUTORTURE_RDR_RBH; | |
| WARN_ON_ONCE(mask >> RCUTORTURE_RDR_SHIFT_1); // Can't have reader idx bits. | |
| /* 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 have nested RCU reader without outer RCU reader. | |
| if (!(mask & RCUTORTURE_RDR_RCU_1) && (mask & RCUTORTURE_RDR_RCU_2)) { | |
| if (oldmask & RCUTORTURE_RDR_RCU_1) | |
| mask &= ~RCUTORTURE_RDR_RCU_2; | |
| else | |
| mask |= RCUTORTURE_RDR_RCU_1; | |
| } | |
| /* | |
| * Don't mess with interrupt masking in interrupt handlers. | |
| */ | |
| if (in_hardirq() || this_cpu_read(torture_in_scf_handler)) | |
| mask &= ~(preempts_irq | bhs); | |
| /* | |
| * Can't enable bh w/irq disabled. | |
| */ | |
| if (mask & RCUTORTURE_RDR_IRQ) | |
| mask |= oldmask & bhs; | |
| /* | |
| * Ideally these sequences would be detected in debug builds | |
| * (regardless of RT), but until then don't stop testing | |
| * them on non-RT. | |
| */ | |
| if (IS_ENABLED(CONFIG_PREEMPT_RT)) { | |
| /* Can't modify BH in atomic context */ | |
| if (oldmask & preempts_irq) | |
| mask &= ~bhs; | |
| if ((oldmask | mask) & preempts_irq) | |
| mask |= oldmask & bhs; | |
| } | |
| return mask ?: RCUTORTURE_RDR_RCU_1; | |
| } | |
| /* | |
| * 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); | |
| i = ((i | (i >> 3)) & RCUTORTURE_RDR_MAX_LOOPS) + 1; | |
| for (j = 0; j < i; j++) { | |
| mask = rcutorture_extend_mask(*readstate, trsp); | |
| WARN_ON_ONCE(mask & RCUTORTURE_RDR_UPDOWN); | |
| rcutorture_one_extend(readstate, mask, trsp, &rtrsp[j]); | |
| } | |
| return &rtrsp[j]; | |
| } | |
| struct rcu_torture_one_read_state { | |
| bool checkpolling; | |
| unsigned long cookie; | |
| struct rcu_gp_seq cookie_full; | |
| unsigned long started; | |
| struct rcu_torture *p; | |
| int readstate; | |
| struct rt_read_seg rtseg[RCUTORTURE_RDR_MAX_SEGS]; | |
| struct rt_read_seg *rtrsp; | |
| unsigned long long ts; | |
| }; | |
| static void rcu_torture_dump_read_segs(struct rt_read_seg *rrsp, int nsegs) | |
| { | |
| bool firsttime; | |
| int i; | |
| int j; | |
| firsttime = 1; | |
| for (i = 0; i < nsegs; i++) { | |
| if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP)) | |
| pr_alert("\t%lluus ", div64_u64(rrsp[i].rt_ts, 1000ULL)); | |
| else | |
| pr_alert("\t"); | |
| pr_cont("%d: %#4x", i, rrsp[i].rt_readstate); | |
| if (rrsp[i].rt_delay_jiffies != 0) { | |
| pr_cont("%s%ldjiffies", firsttime ? "" : "+", | |
| rrsp[i].rt_delay_jiffies); | |
| firsttime = 0; | |
| } | |
| if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_CPU)) { | |
| pr_cont(" CPU %2d", rrsp[i].rt_cpu); | |
| if (rrsp[i].rt_cpu != rrsp[i].rt_end_cpu) | |
| pr_cont("->%-2d", rrsp[i].rt_end_cpu); | |
| else | |
| pr_cont(" ..."); | |
| } | |
| if (IS_ENABLED(CONFIG_RCU_TORTURE_TEST_LOG_GP) && | |
| cur_ops->gather_gp_seqs && cur_ops->format_gp_seqs) { | |
| char buf1[20+1]; | |
| char buf2[20+1]; | |
| char sepchar = '-'; | |
| cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq, buf1, ARRAY_SIZE(buf1)); | |
| cur_ops->format_gp_seqs(rrsp[i].rt_gp_seq_end, buf2, ARRAY_SIZE(buf2)); | |
| if (rrsp[i].rt_gp_seq == rrsp[i].rt_gp_seq_end) { | |
| if (buf2[0]) { | |
| for (j = 0; buf2[j]; j++) | |
| buf2[j] = '.'; | |
| if (j) | |
| buf2[j - 1] = ' '; | |
| } | |
| sepchar = ' '; | |
| } | |
| pr_cont(" %s%c%s", buf1, sepchar, buf2); | |
| } | |
| if (rrsp[i].rt_delay_ms != 0) { | |
| pr_cont(" %s%ldms", firsttime ? "" : "+", rrsp[i].rt_delay_ms); | |
| firsttime = 0; | |
| } | |
| if (rrsp[i].rt_delay_us != 0) { | |
| pr_cont(" %s%ldus", firsttime ? "" : "+", rrsp[i].rt_delay_us); | |
| firsttime = 0; | |
| } | |
| pr_cont("%s", rrsp[i].rt_preempted ? " preempted" : ""); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_BH) | |
| pr_cont(" BH"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_IRQ) | |
| pr_cont(" IRQ"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_PREEMPT) | |
| pr_cont(" PREEMPT"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RBH) | |
| pr_cont(" RBH"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_SCHED) | |
| pr_cont(" SCHED"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_1) | |
| pr_cont(" RCU_1"); | |
| if (rrsp[i].rt_readstate & RCUTORTURE_RDR_RCU_2) | |
| pr_cont(" RCU_2"); | |
| pr_cont("\n"); | |
| } | |
| if (rt_read_preempted) | |
| pr_alert("\tReader was preempted.\n"); | |
| } | |
| static void init_rcu_torture_one_read_state(struct rcu_torture_one_read_state *rtorsp, | |
| struct torture_random_state *trsp) | |
| { | |
| memset(rtorsp, 0, sizeof(*rtorsp)); | |
| rtorsp->checkpolling = !(torture_random(trsp) & 0xfff); | |
| rtorsp->rtrsp = &rtorsp->rtseg[0]; | |
| } | |
| /* | |
| * Set up the first segment of a series of overlapping read-side | |
| * critical sections. The caller must have actually initiated the | |
| * outermost read-side critical section. | |
| */ | |
| static bool rcu_torture_one_read_start(struct rcu_torture_one_read_state *rtorsp, | |
| struct torture_random_state *trsp, long myid) | |
| { | |
| if (rtorsp->checkpolling) { | |
| if (cur_ops->get_gp_state && cur_ops->poll_gp_state) | |
| rtorsp->cookie = cur_ops->get_gp_state(); | |
| if (cur_ops->get_gp_state_full && cur_ops->poll_gp_state_full) | |
| cur_ops->get_gp_state_full(&rtorsp->cookie_full); | |
| } | |
| rtorsp->started = cur_ops->get_gp_seq(); | |
| rtorsp->ts = rcu_trace_clock_local(); | |
| rtorsp->p = rcu_dereference_check(rcu_torture_current, | |
| !cur_ops->readlock_held || cur_ops->readlock_held() || | |
| (rtorsp->readstate & RCUTORTURE_RDR_UPDOWN)); | |
| if (rtorsp->p == NULL) { | |
| /* Wait for rcu_torture_writer to get underway */ | |
| rcutorture_one_extend(&rtorsp->readstate, 0, trsp, rtorsp->rtrsp); | |
| return false; | |
| } | |
| if (rtorsp->p->rtort_mbtest == 0) | |
| atomic_inc(&n_rcu_torture_mberror); | |
| rcu_torture_reader_do_mbchk(myid, rtorsp->p, trsp); | |
| return true; | |
| } | |
| /* | |
| * Complete the last segment of a series of overlapping read-side | |
| * critical sections and check for errors. | |
| */ | |
| static void rcu_torture_one_read_end(struct rcu_torture_one_read_state *rtorsp, | |
| struct torture_random_state *trsp) | |
| { | |
| int i; | |
| unsigned long completed; | |
| int pipe_count; | |
| bool preempted = false; | |
| struct rt_read_seg *rtrsp1; | |
| preempt_disable(); | |
| pipe_count = READ_ONCE(rtorsp->p->rtort_pipe_count); | |
| if (pipe_count > RCU_TORTURE_PIPE_LEN) { | |
| // Should not happen in a correct RCU implementation, | |
| // happens quite often for torture_type=busted. | |
| pipe_count = RCU_TORTURE_PIPE_LEN; | |
| } | |
| completed = cur_ops->get_gp_seq(); | |
| if (pipe_count > 1) { | |
| do_trace_rcu_torture_read(cur_ops->name, &rtorsp->p->rtort_rcu, | |
| rtorsp->ts, rtorsp->started, completed); | |
| rcu_ftrace_dump(DUMP_ALL); | |
| } | |
| this_cpu_inc(rcu_torture_count[pipe_count]); | |
| completed = rcutorture_seq_diff(completed, rtorsp->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 (rtorsp->checkpolling) { | |
| if (cur_ops->get_gp_state && cur_ops->poll_gp_state) | |
| WARN_ONCE(cur_ops->poll_gp_state(rtorsp->cookie), | |
| "%s: Cookie check 2 failed %s(%d) %lu->%lu\n", | |
| __func__, | |
| rcu_torture_writer_state_getname(), | |
| rcu_torture_writer_state, | |
| rtorsp->cookie, cur_ops->get_gp_state()); | |
| if (cur_ops->get_gp_state_full && cur_ops->poll_gp_state_full) | |
| WARN_ONCE(cur_ops->poll_gp_state_full(&rtorsp->cookie_full), | |
| "%s: Cookie check 6 failed %s(%d) online %*pbl\n", | |
| __func__, | |
| rcu_torture_writer_state_getname(), | |
| rcu_torture_writer_state, | |
| cpumask_pr_args(cpu_online_mask)); | |
| } | |
| if (cur_ops->reader_blocked) | |
| preempted = cur_ops->reader_blocked(); | |
| rcutorture_one_extend(&rtorsp->readstate, 0, trsp, rtorsp->rtrsp); | |
| WARN_ON_ONCE(rtorsp->readstate); | |
| // This next splat is expected behavior if leakpointer, especially | |
| // for CONFIG_RCU_STRICT_GRACE_PERIOD=y kernels. | |
| WARN_ON_ONCE(leakpointer && READ_ONCE(rtorsp->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 = &rtorsp->rtseg[0]; rtrsp1 < rtorsp->rtrsp; rtrsp1++) | |
| err_segs[i++] = *rtrsp1; | |
| rt_read_nsegs = i; | |
| rt_read_preempted = preempted; | |
| } | |
| } | |
| /* | |
| * 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) | |
| { | |
| static int firsttime = 1; | |
| int newstate; | |
| unsigned int nsegs; | |
| struct rcu_torture_one_read_state rtors; | |
| WARN_ON_ONCE(!rcu_is_watching()); | |
| init_rcu_torture_one_read_state(&rtors, trsp); | |
| newstate = rcutorture_extend_mask(rtors.readstate, trsp); | |
| WARN_ON_ONCE(newstate & RCUTORTURE_RDR_UPDOWN); | |
| rcutorture_one_extend(&rtors.readstate, newstate, trsp, rtors.rtrsp++); | |
| if (!rcu_torture_one_read_start(&rtors, trsp, myid)) | |
| return false; | |
| rtors.rtrsp = rcutorture_loop_extend(&rtors.readstate, trsp, rtors.rtrsp); | |
| rcu_torture_one_read_end(&rtors, trsp); | |
| // This splat will happen on systems built with CONFIG_IRQ_WORK=n | |
| // and on systems where arch_irq_work_has_interrupt() returns false. | |
| // It might also happen on systems using a short-duration clock | |
| // interrupt instead of a self-IPI (powerpc, s390) or that use | |
| // neither a self-IPI nor a short-duration clock interrupts | |
| // (all architectures using the generic implementation | |
| // of arch_irq_work_raise()). On such systems, RCU cannot | |
| // guarantee to immediately deboost RCU readers when the outermost | |
| // rcu_read_unlock() does not end the full segmented RCU read-side | |
| // critical section. | |
| if (cur_ops->is_task_rcu_boosted && cur_ops->is_task_rcu_boosted() && | |
| !in_serving_softirq() && !in_hardirq() && !in_nmi() && | |
| READ_ONCE(firsttime) && xchg(&firsttime, 0)) { | |
| WARN_ON_ONCE(deboost_timeliness_check); | |
| nsegs = rtors.rtrsp - rtors.rtseg; | |
| nsegs = clamp_val(nsegs, 0, RCUTORTURE_RDR_MAX_SEGS); | |
| pr_alert("Slow-deboost rcutorture reader segments:\n"); | |
| rcu_torture_dump_read_segs(rtors.rtseg, nsegs); | |
| } | |
| 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) | |
| { | |
| WARN_ON_ONCE(!in_serving_softirq()); | |
| WARN_ON_ONCE(in_hardirq()); | |
| WARN_ON_ONCE(in_nmi()); | |
| 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 softirq handler. */ | |
| if (cur_ops->call) { | |
| struct rcu_head *rhp = kmalloc_obj(*rhp, GFP_NOWAIT); | |
| if (rhp) | |
| cur_ops->call(rhp, rcu_torture_timer_cb); | |
| } | |
| } | |
| static DEFINE_TORTURE_RANDOM_PERCPU(rcu_torture_irq_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. | |
| * | |
| * Note that on some systems, "interrupts" from idle are direct calls | |
| * rather than interrupts. The torture_in_scf_handler per-CPU variable | |
| * accounts for this case. | |
| */ | |
| static void rcu_torture_irq(void *unused) | |
| { | |
| WARN_ON_ONCE(in_nmi()); | |
| lockdep_assert_irqs_disabled(); | |
| atomic_long_inc(&n_rcu_torture_irqs); | |
| this_cpu_write(torture_in_scf_handler, true); | |
| (void)rcu_torture_one_read(this_cpu_ptr(&rcu_torture_irq_rand), -1); | |
| this_cpu_write(torture_in_scf_handler, false); | |
| // Test call_rcu() invocation from interrupt handler. Interrupts | |
| // will always be disabled here, even in CONFIG_PREEMPT_RT=y kernels. | |
| // The "right" thing to do would be to create a special-purpose | |
| // lockless or raw-spinlock-protected allocator, but in the meantime, | |
| // skip testing call_rcu() from interrupt handlers in kernels built | |
| // with either CONFIG_PREEMPT_RT=y or CONFIG_PROVE_LOCKING=y. | |
| if (cur_ops->call && !IS_ENABLED(CONFIG_PROVE_LOCKING) && !IS_ENABLED(CONFIG_PREEMPT_RT)) { | |
| struct rcu_head *rhp = kmalloc_obj(*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 lastscf = jiffies; | |
| 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); // CPU bound, so need tick. | |
| do { | |
| if (irqreader && cur_ops->irq_capable) { | |
| if (!timer_pending(&t)) { | |
| int cpu; | |
| mod_timer(&t, jiffies + 1); | |
| preempt_disable(); | |
| cpu = torture_random(&rand) % nr_cpu_ids; | |
| if (!cpu_online(cpu)) { | |
| cpu = cpumask_next(cpu, cpu_online_mask); | |
| if (cpu >= nr_cpu_ids) | |
| cpu = cpumask_next(-1, cpu_online_mask); | |
| } | |
| // An smp_call_function_single() to self is not an interrupt! | |
| if (cpu != smp_processor_id() && | |
| time_after(jiffies, lastscf + HZ * nrealreaders / 50)) { | |
| smp_call_function_single(cpu, rcu_torture_irq, NULL, 0); | |
| lastscf = jiffies; | |
| } | |
| preempt_enable(); | |
| } | |
| } | |
| 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_must_stop() && | |
| (torture_num_online_cpus() < mynumonline || !rcu_inkernel_boot_has_ended())) | |
| schedule_timeout_interruptible(HZ / 5); | |
| stutter_wait("rcu_torture_reader"); | |
| } while (!torture_must_stop()); | |
| if (irqreader && cur_ops->irq_capable) { | |
| timer_delete_sync(&t); | |
| timer_destroy_on_stack(&t); | |
| } | |
| tick_dep_clear_task(current, TICK_DEP_BIT_RCU); | |
| torture_kthread_stopping("rcu_torture_reader"); | |
| return 0; | |
| } | |
| struct rcu_torture_one_read_state_updown { | |
| struct hrtimer rtorsu_hrt; | |
| bool rtorsu_inuse; | |
| ktime_t rtorsu_kt; | |
| int rtorsu_cpu; | |
| unsigned long rtorsu_j; | |
| unsigned long rtorsu_ndowns; | |
| unsigned long rtorsu_nups; | |
| unsigned long rtorsu_nmigrates; | |
| struct torture_random_state rtorsu_trs; | |
| struct rcu_torture_one_read_state rtorsu_rtors; | |
| }; | |
| static struct rcu_torture_one_read_state_updown *updownreaders; | |
| static DEFINE_TORTURE_RANDOM(rcu_torture_updown_rand); | |
| static int rcu_torture_updown(void *arg); | |
| static enum hrtimer_restart rcu_torture_updown_hrt(struct hrtimer *hrtp) | |
| { | |
| int cpu = raw_smp_processor_id(); | |
| struct rcu_torture_one_read_state_updown *rtorsup; | |
| rtorsup = container_of(hrtp, struct rcu_torture_one_read_state_updown, rtorsu_hrt); | |
| rcu_torture_one_read_end(&rtorsup->rtorsu_rtors, &rtorsup->rtorsu_trs); | |
| WARN_ONCE(rtorsup->rtorsu_nups >= rtorsup->rtorsu_ndowns, "%s: Up without matching down #%zu.\n", __func__, rtorsup - updownreaders); | |
| WRITE_ONCE(rtorsup->rtorsu_nups, rtorsup->rtorsu_nups + 1); | |
| WRITE_ONCE(rtorsup->rtorsu_nmigrates, | |
| rtorsup->rtorsu_nmigrates + (cpu != rtorsup->rtorsu_cpu)); | |
| smp_store_release(&rtorsup->rtorsu_inuse, false); | |
| return HRTIMER_NORESTART; | |
| } | |
| static int rcu_torture_updown_init(void) | |
| { | |
| int i; | |
| struct torture_random_state *rand = &rcu_torture_updown_rand; | |
| int ret; | |
| if (n_up_down < 0) | |
| return 0; | |
| if (!srcu_torture_have_up_down()) { | |
| VERBOSE_TOROUT_STRING("rcu_torture_updown_init: Disabling up/down reader tests due to lack of primitives"); | |
| return 0; | |
| } | |
| updownreaders = kzalloc_objs(*updownreaders, n_up_down); | |
| if (!updownreaders) { | |
| VERBOSE_TOROUT_STRING("rcu_torture_updown_init: Out of memory, disabling up/down reader tests"); | |
| return -ENOMEM; | |
| } | |
| for (i = 0; i < n_up_down; i++) { | |
| init_rcu_torture_one_read_state(&updownreaders[i].rtorsu_rtors, rand); | |
| hrtimer_setup(&updownreaders[i].rtorsu_hrt, rcu_torture_updown_hrt, CLOCK_MONOTONIC, | |
| HRTIMER_MODE_REL | HRTIMER_MODE_HARD); | |
| torture_random_init(&updownreaders[i].rtorsu_trs); | |
| init_rcu_torture_one_read_state(&updownreaders[i].rtorsu_rtors, | |
| &updownreaders[i].rtorsu_trs); | |
| } | |
| ret = torture_create_kthread(rcu_torture_updown, rand, updown_task); | |
| if (ret) { | |
| kfree(updownreaders); | |
| updownreaders = NULL; | |
| } | |
| return ret; | |
| } | |
| static void rcu_torture_updown_cleanup(void) | |
| { | |
| struct rcu_torture_one_read_state_updown *rtorsup; | |
| for (rtorsup = updownreaders; rtorsup < &updownreaders[n_up_down]; rtorsup++) { | |
| if (!smp_load_acquire(&rtorsup->rtorsu_inuse)) | |
| continue; | |
| if (hrtimer_cancel(&rtorsup->rtorsu_hrt) || WARN_ON_ONCE(rtorsup->rtorsu_inuse)) { | |
| rcu_torture_one_read_end(&rtorsup->rtorsu_rtors, &rtorsup->rtorsu_trs); | |
| WARN_ONCE(rtorsup->rtorsu_nups >= rtorsup->rtorsu_ndowns, "%s: Up without matching down #%zu.\n", __func__, rtorsup - updownreaders); | |
| WRITE_ONCE(rtorsup->rtorsu_nups, rtorsup->rtorsu_nups + 1); | |
| smp_store_release(&rtorsup->rtorsu_inuse, false); | |
| } | |
| } | |
| kfree(updownreaders); | |
| updownreaders = NULL; | |
| } | |
| // Do one reader for rcu_torture_updown(). | |
| static void rcu_torture_updown_one(struct rcu_torture_one_read_state_updown *rtorsup) | |
| { | |
| int idx; | |
| int rawidx; | |
| ktime_t t; | |
| init_rcu_torture_one_read_state(&rtorsup->rtorsu_rtors, &rtorsup->rtorsu_trs); | |
| rawidx = cur_ops->down_read(); | |
| WRITE_ONCE(rtorsup->rtorsu_ndowns, rtorsup->rtorsu_ndowns + 1); | |
| idx = (rawidx << RCUTORTURE_RDR_SHIFT_1) & RCUTORTURE_RDR_MASK_1; | |
| rtorsup->rtorsu_rtors.readstate = idx | RCUTORTURE_RDR_UPDOWN; | |
| rtorsup->rtorsu_rtors.rtrsp++; | |
| rtorsup->rtorsu_cpu = raw_smp_processor_id(); | |
| if (!rcu_torture_one_read_start(&rtorsup->rtorsu_rtors, &rtorsup->rtorsu_trs, -1)) { | |
| WARN_ONCE(rtorsup->rtorsu_nups >= rtorsup->rtorsu_ndowns, "%s: Up without matching down #%zu.\n", __func__, rtorsup - updownreaders); | |
| WRITE_ONCE(rtorsup->rtorsu_nups, rtorsup->rtorsu_nups + 1); | |
| schedule_timeout_idle(HZ); | |
| return; | |
| } | |
| smp_store_release(&rtorsup->rtorsu_inuse, true); | |
| t = torture_random(&rtorsup->rtorsu_trs) & 0xfffff; // One per million. | |
| if (t < 10 * 1000) | |
| t = 200 * 1000 * 1000; | |
| hrtimer_start(&rtorsup->rtorsu_hrt, t, HRTIMER_MODE_REL | HRTIMER_MODE_HARD); | |
| smp_mb(); // Sample jiffies after posting hrtimer. | |
| rtorsup->rtorsu_j = jiffies; // Not used by hrtimer handler. | |
| rtorsup->rtorsu_kt = t; | |
| } | |
| /* | |
| * RCU torture up/down reader kthread, starting RCU readers in kthread | |
| * context and ending them in hrtimer handlers. Otherwise similar to | |
| * rcu_torture_reader(). | |
| */ | |
| static int | |
| rcu_torture_updown(void *arg) | |
| { | |
| unsigned long j; | |
| struct rcu_torture_one_read_state_updown *rtorsup; | |
| VERBOSE_TOROUT_STRING("rcu_torture_updown task started"); | |
| do { | |
| for (rtorsup = updownreaders; rtorsup < &updownreaders[n_up_down]; rtorsup++) { | |
| if (torture_must_stop()) | |
| break; | |
| j = smp_load_acquire(&jiffies); // Time before ->rtorsu_inuse. | |
| if (smp_load_acquire(&rtorsup->rtorsu_inuse)) { | |
| WARN_ONCE(time_after(j, rtorsup->rtorsu_j + 1 + HZ * 10), | |
| "hrtimer queued at jiffies %lu for %lld ns took %lu jiffies\n", rtorsup->rtorsu_j, rtorsup->rtorsu_kt, j - rtorsup->rtorsu_j); | |
| continue; | |
| } | |
| rcu_torture_updown_one(rtorsup); | |
| } | |
| torture_hrtimeout_ms(1, 1000, &rcu_torture_updown_rand); | |
| stutter_wait("rcu_torture_updown"); | |
| } while (!torture_must_stop()); | |
| rcu_torture_updown_cleanup(); | |
| torture_kthread_stopping("rcu_torture_updown"); | |
| 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_possible_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 >> 1) % (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 }; | |
| long n_gpwraps = 0; | |
| unsigned long ndowns = 0; | |
| unsigned long nunexpired = 0; | |
| unsigned long nmigrates = 0; | |
| unsigned long nups = 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]); | |
| } | |
| if (cur_ops->get_gpwrap_count) | |
| n_gpwraps += cur_ops->get_gpwrap_count(cpu); | |
| } | |
| if (updownreaders) { | |
| for (i = 0; i < n_up_down; i++) { | |
| ndowns += READ_ONCE(updownreaders[i].rtorsu_ndowns); | |
| nups += READ_ONCE(updownreaders[i].rtorsu_nups); | |
| nunexpired += READ_ONCE(updownreaders[i].rtorsu_inuse); | |
| nmigrates += READ_ONCE(updownreaders[i].rtorsu_nmigrates); | |
| } | |
| } | |
| for (i = RCU_TORTURE_PIPE_LEN; i >= 0; i--) { | |
| if (pipesummary[i] != 0) | |
| break; | |
| } // The value of variable "i" is used later, so don't clobber it! | |
| 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 ", | |
| 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); | |
| pr_cont("rtbf: %ld rtb: %ld nt: %ld ni: %ld ", | |
| n_rcu_torture_boost_failure, | |
| n_rcu_torture_boosts, | |
| atomic_long_read(&n_rcu_torture_timers), | |
| atomic_long_read(&n_rcu_torture_irqs)); | |
| if (updownreaders) | |
| pr_cont("ndowns: %lu nups: %lu nhrt: %lu nmigrates: %lu ", ndowns, nups, nunexpired, nmigrates); | |
| 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 ", | |
| atomic_long_read(&n_nocb_offload), atomic_long_read(&n_nocb_deoffload)); | |
| pr_cont("gpwraps: %ld\n", n_gpwraps); | |
| 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_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_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() && | |
| rcu_inkernel_boot_has_ended()) { | |
| int __maybe_unused flags = 0; | |
| unsigned long __maybe_unused gp_seq = 0; | |
| if (cur_ops->get_gp_data) | |
| cur_ops->get_gp_data(&flags, &gp_seq); | |
| wtp = READ_ONCE(writer_task); | |
| pr_alert("??? Writer stall state %s(%d) g%lu f%#x ->state %c cpu %d\n", | |
| rcu_torture_writer_state_getname(), | |
| rcu_torture_writer_state, gp_seq, flags, | |
| wtp == NULL ? '?' : task_state_to_char(wtp), | |
| 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); | |
| if (WARN_ON_ONCE(!kcp)) | |
| return; | |
| rhp = kmem_cache_alloc(kcp, GFP_KERNEL); | |
| if (WARN_ON_ONCE(!rhp)) { | |
| kmem_cache_destroy(kcp); | |
| return; | |
| } | |
| 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_obj(*rhp); | |
| if (WARN_ON_ONCE(!rhp)) | |
| return; | |
| 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); | |
| if (WARN_ON_ONCE(!rhp)) | |
| return; | |
| 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 nwriters=%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 test_boost_holdoff=%d shutdown_secs=%d " | |
| "stall_cpu=%d stall_cpu_holdoff=%d stall_cpu_irqsoff=%d " | |
| "stall_cpu_block=%d stall_cpu_repeat=%d " | |
| "n_barrier_cbs=%d " | |
| "onoff_interval=%d onoff_holdoff=%d " | |
| "read_exit_delay=%d read_exit_burst=%d " | |
| "reader_flavor=%x " | |
| "nocbs_nthreads=%d nocbs_toggle=%d " | |
| "test_nmis=%d " | |
| "preempt_duration=%d preempt_interval=%d n_up_down=%d\n", | |
| torture_type, tag, nrealreaders, nwriters, nrealfakewriters, | |
| 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, test_boost_holdoff, shutdown_secs, | |
| stall_cpu, stall_cpu_holdoff, stall_cpu_irqsoff, | |
| stall_cpu_block, stall_cpu_repeat, | |
| n_barrier_cbs, | |
| onoff_interval, onoff_holdoff, | |
| read_exit_delay, read_exit_burst, | |
| reader_flavor, | |
| nocbs_nthreads, nocbs_toggle, | |
| test_nmis, | |
| preempt_duration, preempt_interval, n_up_down); | |
| } | |
| 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. */ | |
| // 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)) { | |
| 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); | |
| #ifdef CONFIG_IRQ_FORCED_THREADING | |
| if (force_irqthreads()) { | |
| t = per_cpu(ktimerd, cpu); | |
| WARN_ON_ONCE(!t); | |
| sp.sched_priority = 2; | |
| sched_setscheduler_nocheck(t, SCHED_FIFO, &sp); | |
| } | |
| #endif | |
| } | |
| /* 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_run_on_cpu(rcu_torture_boost, NULL, | |
| cpu, "rcu_torture_boost_%u"); | |
| 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; | |
| } | |
| mutex_unlock(&boost_mutex); | |
| return 0; | |
| } | |
| static int rcu_torture_stall_nf(struct notifier_block *nb, unsigned long v, void *ptr) | |
| { | |
| pr_info("%s: v=%lu, duration=%lu.\n", __func__, v, (unsigned long)ptr); | |
| return NOTIFY_OK; | |
| } | |
| static struct notifier_block rcu_torture_stall_block = { | |
| .notifier_call = rcu_torture_stall_nf, | |
| }; | |
| /* | |
| * CPU-stall kthread. It waits as specified by stall_cpu_holdoff, then | |
| * induces a CPU stall for the time specified by stall_cpu. If a new | |
| * stall test is added, stallsdone in rcu_torture_writer() must be adjusted. | |
| */ | |
| static void rcu_torture_stall_one(int rep, int irqsoff) | |
| { | |
| int idx; | |
| unsigned long stop_at; | |
| 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 (irqsoff) | |
| local_irq_disable(); | |
| else if (!stall_cpu_block) | |
| preempt_disable(); | |
| pr_alert("%s start stall episode %d on CPU %d.\n", | |
| __func__, rep + 1, raw_smp_processor_id()); | |
| while (ULONG_CMP_LT((unsigned long)ktime_get_seconds(), stop_at) && | |
| !kthread_should_stop()) | |
| if (stall_cpu_block) { | |
| #ifdef CONFIG_PREEMPTION | |
| preempt_schedule(); | |
| #else | |
| schedule_timeout_uninterruptible(HZ); | |
| #endif | |
| } else if (stall_no_softlockup) { | |
| touch_softlockup_watchdog(); | |
| } | |
| if (irqsoff) | |
| local_irq_enable(); | |
| else if (!stall_cpu_block) | |
| preempt_enable(); | |
| cur_ops->readunlock(idx); | |
| } | |
| } | |
| /* | |
| * CPU-stall kthread. Invokes rcu_torture_stall_one() once, and then as many | |
| * additional times as specified by the stall_cpu_repeat module parameter. | |
| * Note that stall_cpu_irqsoff is ignored on the second and subsequent | |
| * stall. | |
| */ | |
| static int rcu_torture_stall(void *args) | |
| { | |
| int i; | |
| int repeat = stall_cpu_repeat; | |
| int ret; | |
| VERBOSE_TOROUT_STRING("rcu_torture_stall task started"); | |
| if (repeat < 0) { | |
| repeat = 0; | |
| WARN_ON_ONCE(IS_BUILTIN(CONFIG_RCU_TORTURE_TEST)); | |
| } | |
| if (rcu_cpu_stall_notifiers) { | |
| ret = rcu_stall_chain_notifier_register(&rcu_torture_stall_block); | |
| if (ret) | |
| pr_info("%s: rcu_stall_chain_notifier_register() returned %d, %sexpected.\n", | |
| __func__, ret, !IS_ENABLED(CONFIG_RCU_STALL_COMMON) ? "un" : ""); | |
| } | |
| for (i = 0; i <= repeat; i++) { | |
| if (kthread_should_stop()) | |
| break; | |
| rcu_torture_stall_one(i, i == 0 ? stall_cpu_irqsoff : 0); | |
| } | |
| pr_alert("%s end.\n", __func__); | |
| if (rcu_cpu_stall_notifiers && !ret) { | |
| ret = rcu_stall_chain_notifier_unregister(&rcu_torture_stall_block); | |
| if (ret) | |
| pr_info("%s: rcu_stall_chain_notifier_unregister() returned %d.\n", __func__, ret); | |
| } | |
| 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; | |
| int rcu_fwd_id; | |
| }; | |
| static DEFINE_MUTEX(rcu_fwd_mutex); | |
| static struct rcu_fwd *rcu_fwds; | |
| static unsigned long rcu_fwd_seq; | |
| static atomic_long_t rcu_fwd_max_cbs; | |
| 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 %d (duration %lu jiffies):", | |
| __func__, rfp->rcu_fwd_id, 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; | |
| smp_store_release(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_eqs(); | |
| 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); | |
| pr_alert("%s: Starting forward-progress test %d\n", __func__, rfp->rcu_fwd_id); | |
| 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(). */ | |
| atomic_inc(&rcu_fwd_cb_nodelay); | |
| 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: %d Duration %ld cver %ld gps %ld\n", __func__, | |
| rfp->rcu_fwd_id, dur, cver, gps); | |
| } | |
| if (selfpropcb) { | |
| WRITE_ONCE(fcs.stop, 1); | |
| cur_ops->sync(); /* Wait for running CB to complete. */ | |
| pr_alert("%s: Waiting for CBs: %pS() %d\n", __func__, cur_ops->cb_barrier, rfp->rcu_fwd_id); | |
| 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. */ | |
| atomic_dec(&rcu_fwd_cb_nodelay); | |
| } | |
| /* 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)) { | |
| pr_alert("%s: Emergency stop, so no forward-progress test %d\n", __func__, rfp->rcu_fwd_id); | |
| return; /* Get out of the way quickly, no GP wait! */ | |
| } | |
| if (!cur_ops->call) { | |
| pr_alert("%s: No ->call(), so no forward-progress test %d\n", __func__, rfp->rcu_fwd_id); | |
| return; /* Can't do call_rcu() fwd prog without ->call. */ | |
| } | |
| /* Loop continuously posting RCU callbacks. */ | |
| pr_alert("%s: Starting forward-progress test %d\n", __func__, rfp->rcu_fwd_id); | |
| atomic_inc(&rcu_fwd_cb_nodelay); | |
| 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); // CPU bound, so need tick. | |
| 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 if (!cur_ops->cbflood_max || cur_ops->cbflood_max > n_max_cbs) { | |
| rfcp = kmalloc_obj(*rfcp); | |
| 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; | |
| } else { | |
| rfcp = NULL; | |
| } | |
| if (rfcp) | |
| 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_eqs(); | |
| 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); | |
| pr_alert("%s: Waiting for CBs: %pS() %d\n", __func__, cur_ops->cb_barrier, rfp->rcu_fwd_id); | |
| 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()) { | |
| if (WARN_ON(n_max_gps < MIN_FWD_CBS_LAUNDERED) && cur_ops->gp_kthread_dbg) | |
| cur_ops->gp_kthread_dbg(); | |
| 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 #online %u\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, num_online_cpus()); | |
| atomic_long_add(n_max_cbs, &rcu_fwd_max_cbs); | |
| mutex_lock(&rcu_fwd_mutex); // Serialize histograms. | |
| rcu_torture_fwd_cb_hist(rfp); | |
| mutex_unlock(&rcu_fwd_mutex); | |
| } | |
| schedule_timeout_uninterruptible(HZ); /* Let CBs drain. */ | |
| tick_dep_clear_task(current, TICK_DEP_BIT_RCU); | |
| atomic_dec(&rcu_fwd_cb_nodelay); | |
| } | |
| /* | |
| * 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) | |
| { | |
| int i; | |
| long ncbs; | |
| 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__); | |
| for (i = 0; i < fwd_progress; i++) { | |
| rcu_torture_fwd_cb_hist(&rfp[i]); | |
| rcu_fwd_progress_check(1 + (jiffies - READ_ONCE(rfp[i].rcu_fwd_startat)) / 2); | |
| } | |
| WRITE_ONCE(rcu_fwd_emergency_stop, true); | |
| smp_mb(); /* Emergency stop before free and wait to avoid hangs. */ | |
| ncbs = 0; | |
| for (i = 0; i < fwd_progress; i++) | |
| ncbs += rcu_torture_fwd_prog_cbfree(&rfp[i]); | |
| pr_info("%s: Freed %lu RCU callbacks.\n", __func__, ncbs); | |
| cur_ops->cb_barrier(); | |
| ncbs = 0; | |
| for (i = 0; i < fwd_progress; i++) | |
| ncbs += rcu_torture_fwd_prog_cbfree(&rfp[i]); | |
| pr_info("%s: Freed %lu RCU callbacks.\n", __func__, ncbs); | |
| cur_ops->cb_barrier(); | |
| ncbs = 0; | |
| for (i = 0; i < fwd_progress; i++) | |
| ncbs += rcu_torture_fwd_prog_cbfree(&rfp[i]); | |
| pr_info("%s: Freed %lu RCU callbacks.\n", __func__, ncbs); | |
| 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) | |
| { | |
| bool firsttime = true; | |
| long max_cbs; | |
| int oldnice = task_nice(current); | |
| unsigned long oldseq = READ_ONCE(rcu_fwd_seq); | |
| struct rcu_fwd *rfp = args; | |
| int tested = 0; | |
| int tested_tries = 0; | |
| VERBOSE_TOROUT_STRING("rcu_torture_fwd_progress task started"); | |
| while (!rcu_inkernel_boot_has_ended()) | |
| schedule_timeout_interruptible(HZ / 10); | |
| rcu_bind_current_to_nocb(); | |
| if (!IS_ENABLED(CONFIG_SMP) || !IS_ENABLED(CONFIG_RCU_BOOST)) | |
| set_user_nice(current, MAX_NICE); | |
| do { | |
| if (!rfp->rcu_fwd_id) { | |
| schedule_timeout_interruptible(fwd_progress_holdoff * HZ); | |
| WRITE_ONCE(rcu_fwd_emergency_stop, false); | |
| if (!firsttime) { | |
| max_cbs = atomic_long_xchg(&rcu_fwd_max_cbs, 0); | |
| pr_alert("%s n_max_cbs: %ld\n", __func__, max_cbs); | |
| } | |
| firsttime = false; | |
| WRITE_ONCE(rcu_fwd_seq, rcu_fwd_seq + 1); | |
| } else { | |
| while (READ_ONCE(rcu_fwd_seq) == oldseq && !torture_must_stop()) | |
| schedule_timeout_interruptible(HZ / 20); | |
| oldseq = READ_ONCE(rcu_fwd_seq); | |
| } | |
| pr_alert("%s: Starting forward-progress test %d\n", __func__, rfp->rcu_fwd_id); | |
| if (rcu_inkernel_boot_has_ended() && torture_num_online_cpus() > rfp->rcu_fwd_id) | |
| rcu_torture_fwd_prog_cr(rfp); | |
| if ((cur_ops->stall_dur && cur_ops->stall_dur() > 0) && | |
| (!IS_ENABLED(CONFIG_TINY_RCU) || | |
| (rcu_inkernel_boot_has_ended() && | |
| torture_num_online_cpus() > rfp->rcu_fwd_id))) | |
| rcu_torture_fwd_prog_nr(rfp, &tested, &tested_tries); | |
| /* 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. */ | |
| if (!rfp->rcu_fwd_id) { | |
| 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) | |
| { | |
| int i; | |
| int ret = 0; | |
| struct rcu_fwd *rfp; | |
| if (!fwd_progress) | |
| return 0; /* Not requested, so don't do it. */ | |
| if (fwd_progress >= nr_cpu_ids) { | |
| VERBOSE_TOROUT_STRING("rcu_torture_fwd_prog_init: Limiting fwd_progress to # CPUs.\n"); | |
| fwd_progress = nr_cpu_ids; | |
| } else if (fwd_progress < 0) { | |
| fwd_progress = nr_cpu_ids; | |
| } | |
| if ((!cur_ops->sync && !cur_ops->call) || | |
| (!cur_ops->cbflood_max && (!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"); | |
| fwd_progress = 0; | |
| return 0; | |
| } | |
| if (stall_cpu > 0 || (preempt_duration > 0 && IS_ENABLED(CONFIG_RCU_NOCB_CPU))) { | |
| VERBOSE_TOROUT_STRING("rcu_torture_fwd_prog_init: Disabled, conflicts with CPU-stall and/or preemption testing"); | |
| fwd_progress = 0; | |
| if (IS_MODULE(CONFIG_RCU_TORTURE_TEST)) | |
| return -EINVAL; /* In module, can fail back to user. */ | |
| WARN_ON(1); /* Make sure rcutorture scripting notices conflict. */ | |
| return 0; | |
| } | |
| if (fwd_progress_holdoff <= 0) | |
| fwd_progress_holdoff = 1; | |
| if (fwd_progress_div <= 0) | |
| fwd_progress_div = 4; | |
| rfp = kzalloc_objs(*rfp, fwd_progress); | |
| fwd_prog_tasks = kzalloc_objs(*fwd_prog_tasks, fwd_progress); | |
| if (!rfp || !fwd_prog_tasks) { | |
| kfree(rfp); | |
| kfree(fwd_prog_tasks); | |
| fwd_prog_tasks = NULL; | |
| fwd_progress = 0; | |
| return -ENOMEM; | |
| } | |
| for (i = 0; i < fwd_progress; i++) { | |
| spin_lock_init(&rfp[i].rcu_fwd_lock); | |
| rfp[i].rcu_fwd_cb_tail = &rfp[i].rcu_fwd_cb_head; | |
| rfp[i].rcu_fwd_id = i; | |
| } | |
| mutex_lock(&rcu_fwd_mutex); | |
| rcu_fwds = rfp; | |
| mutex_unlock(&rcu_fwd_mutex); | |
| register_oom_notifier(&rcutorture_oom_nb); | |
| for (i = 0; i < fwd_progress; i++) { | |
| ret = torture_create_kthread(rcu_torture_fwd_prog, &rcu_fwds[i], fwd_prog_tasks[i]); | |
| if (ret) { | |
| fwd_progress = i; | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void rcu_torture_fwd_prog_cleanup(void) | |
| { | |
| int i; | |
| struct rcu_fwd *rfp; | |
| if (!rcu_fwds || !fwd_prog_tasks) | |
| return; | |
| for (i = 0; i < fwd_progress; i++) | |
| torture_stop_kthread(rcu_torture_fwd_prog, fwd_prog_tasks[i]); | |
| unregister_oom_notifier(&rcutorture_oom_nb); | |
| mutex_lock(&rcu_fwd_mutex); | |
| rfp = rcu_fwds; | |
| rcu_fwds = NULL; | |
| mutex_unlock(&rcu_fwd_mutex); | |
| kfree(rfp); | |
| kfree(fwd_prog_tasks); | |
| fwd_prog_tasks = NULL; | |
| } | |
| /* 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 int rcu_torture_barrier1cb(void *rcu_void) | |
| { | |
| struct rcu_head *rhp = rcu_void; | |
| cur_ops->call(rhp, rcu_torture_barrier_cbf); | |
| return 0; | |
| } | |
| /* 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_on_cpu(myid, rcu_torture_barrier1cb, &rcu, 1)) | |
| 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 = | |
| kzalloc_objs(barrier_cbs_tasks[0], n_barrier_cbs); | |
| barrier_cbs_wq = | |
| kzalloc_objs(barrier_cbs_wq[0], n_barrier_cbs); | |
| 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(HZ / 20); | |
| (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) | |
| { | |
| 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 { | |
| VERBOSE_TOROUT_STRING("rcu_torture_read_exit: Start of episode"); | |
| for (i = 0; i < read_exit_burst; i++) { | |
| if (READ_ONCE(read_exit_child_stop)) | |
| break; | |
| stutter_wait("rcu_torture_read_exit"); | |
| // Spawn child. | |
| tsp = kthread_run(rcu_torture_read_exit_child, | |
| &trs, "%s", "rcu_torture_read_exit_child"); | |
| if (IS_ERR(tsp)) { | |
| TOROUT_ERRSTRING("out of memory"); | |
| errexit = true; | |
| break; | |
| } | |
| cond_resched(); | |
| kthread_stop(tsp); | |
| n_read_exits++; | |
| } | |
| VERBOSE_TOROUT_STRING("rcu_torture_read_exit: End of episode"); | |
| rcu_barrier(); // Wait for task_struct free, avoid OOM. | |
| i = 0; | |
| for (; !errexit && !READ_ONCE(read_exit_child_stop) && i < read_exit_delay; i++) | |
| schedule_timeout_uninterruptible(HZ); | |
| } 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(HZ / 20); | |
| torture_kthread_stopping("rcu_torture_read_exit"); | |
| return 0; | |
| } | |
| static int rcu_torture_read_exit_init(void) | |
| { | |
| if (read_exit_burst <= 0) | |
| return 0; | |
| 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 void rcutorture_test_nmis(int n) | |
| { | |
| #if IS_BUILTIN(CONFIG_RCU_TORTURE_TEST) | |
| int cpu; | |
| int dumpcpu; | |
| int i; | |
| for (i = 0; i < n; i++) { | |
| preempt_disable(); | |
| cpu = smp_processor_id(); | |
| dumpcpu = cpu + 1; | |
| if (dumpcpu >= nr_cpu_ids) | |
| dumpcpu = 0; | |
| pr_alert("%s: CPU %d invoking dump_cpu_task(%d)\n", __func__, cpu, dumpcpu); | |
| dump_cpu_task(dumpcpu); | |
| preempt_enable(); | |
| schedule_timeout_uninterruptible(15 * HZ); | |
| } | |
| #else // #if IS_BUILTIN(CONFIG_RCU_TORTURE_TEST) | |
| WARN_ONCE(n, "Non-zero rcutorture.test_nmis=%d permitted only when rcutorture is built in.\n", test_nmis); | |
| #endif // #else // #if IS_BUILTIN(CONFIG_RCU_TORTURE_TEST) | |
| } | |
| // Randomly preempt online CPUs. | |
| static int rcu_torture_preempt(void *unused) | |
| { | |
| int cpu = -1; | |
| DEFINE_TORTURE_RANDOM(rand); | |
| schedule_timeout_idle(stall_cpu_holdoff); | |
| do { | |
| // Wait for preempt_interval ms with up to 100us fuzz. | |
| torture_hrtimeout_ms(preempt_interval, 100, &rand); | |
| // Select online CPU. | |
| cpu = cpumask_next_wrap(cpu, cpu_online_mask); | |
| WARN_ON_ONCE(cpu >= nr_cpu_ids); | |
| // Move to that CPU, if can't do so, retry later. | |
| if (torture_sched_setaffinity(current->pid, cpumask_of(cpu), false)) | |
| continue; | |
| // Preempt at high-ish priority, then reset to normal. | |
| sched_set_fifo(current); | |
| torture_sched_setaffinity(current->pid, cpu_present_mask, true); | |
| mdelay(preempt_duration); | |
| sched_set_normal(current, 0); | |
| stutter_wait("rcu_torture_preempt"); | |
| } while (!torture_must_stop()); | |
| torture_kthread_stopping("rcu_torture_preempt"); | |
| return 0; | |
| } | |
| static enum cpuhp_state rcutor_hp; | |
| static struct hrtimer gpwrap_lag_timer; | |
| static bool gpwrap_lag_active; | |
| /* Timer handler for toggling RCU grace-period sequence overflow test lag value */ | |
| static enum hrtimer_restart rcu_gpwrap_lag_timer(struct hrtimer *timer) | |
| { | |
| ktime_t next_delay; | |
| if (gpwrap_lag_active) { | |
| pr_alert("rcu-torture: Disabling gpwrap lag (value=0)\n"); | |
| cur_ops->set_gpwrap_lag(0); | |
| gpwrap_lag_active = false; | |
| next_delay = ktime_set((gpwrap_lag_cycle_mins - gpwrap_lag_active_mins) * 60, 0); | |
| } else { | |
| pr_alert("rcu-torture: Enabling gpwrap lag (value=%d)\n", gpwrap_lag_gps); | |
| cur_ops->set_gpwrap_lag(gpwrap_lag_gps); | |
| gpwrap_lag_active = true; | |
| next_delay = ktime_set(gpwrap_lag_active_mins * 60, 0); | |
| } | |
| if (torture_must_stop_irq()) | |
| return HRTIMER_NORESTART; | |
| hrtimer_forward_now(timer, next_delay); | |
| return HRTIMER_RESTART; | |
| } | |
| static int rcu_gpwrap_lag_init(void) | |
| { | |
| if (!gpwrap_lag) | |
| return 0; | |
| if (gpwrap_lag_cycle_mins <= 0 || gpwrap_lag_active_mins <= 0) { | |
| pr_alert("rcu-torture: lag timing parameters must be positive\n"); | |
| return -EINVAL; | |
| } | |
| hrtimer_setup(&gpwrap_lag_timer, rcu_gpwrap_lag_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | |
| gpwrap_lag_active = false; | |
| hrtimer_start(&gpwrap_lag_timer, | |
| ktime_set((gpwrap_lag_cycle_mins - gpwrap_lag_active_mins) * 60, 0), HRTIMER_MODE_REL); | |
| return 0; | |
| } | |
| static void rcu_gpwrap_lag_cleanup(void) | |
| { | |
| hrtimer_cancel(&gpwrap_lag_timer); | |
| cur_ops->set_gpwrap_lag(0); | |
| gpwrap_lag_active = false; | |
| } | |
| static void | |
| rcu_torture_cleanup(void) | |
| { | |
| int flags = 0; | |
| unsigned long gp_seq = 0; | |
| int i; | |
| if (torture_cleanup_begin()) { | |
| if (cur_ops->cb_barrier != NULL) { | |
| pr_info("%s: Invoking %pS().\n", __func__, cur_ops->cb_barrier); | |
| cur_ops->cb_barrier(); | |
| } | |
| if (cur_ops->gp_slow_unregister) | |
| cur_ops->gp_slow_unregister(NULL); | |
| return; | |
| } | |
| if (!cur_ops) { | |
| torture_cleanup_end(); | |
| return; | |
| } | |
| rcutorture_test_nmis(test_nmis); | |
| if (cur_ops->gp_kthread_dbg) | |
| cur_ops->gp_kthread_dbg(); | |
| torture_stop_kthread(rcu_torture_preempt, preempt_task); | |
| 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 (updown_task) { | |
| torture_stop_kthread(rcu_torture_updown, updown_task); | |
| updown_task = 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 < nrealfakewriters; i++) | |
| torture_stop_kthread(rcu_torture_fakewriter, | |
| fakewriter_tasks[i]); | |
| kfree(fakewriter_tasks); | |
| fakewriter_tasks = NULL; | |
| } | |
| if (cur_ops->get_gp_data) | |
| cur_ops->get_gp_data(&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() && rcutor_hp >= 0) | |
| 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) { | |
| pr_info("%s: Invoking %pS().\n", __func__, cur_ops->cb_barrier); | |
| 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"); | |
| else | |
| rcu_torture_dump_read_segs(err_segs, rt_read_nsegs); | |
| } | |
| 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(); | |
| if (cur_ops->gp_slow_unregister) | |
| cur_ops->gp_slow_unregister(NULL); | |
| if (gpwrap_lag && cur_ops->set_gpwrap_lag) | |
| rcu_gpwrap_lag_cleanup(); | |
| } | |
| 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); | |
| } | |
| /* | |
| * 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) | |
| { | |
| struct rcu_head rh1; | |
| struct rcu_head rh2; | |
| int idx; | |
| if (!IS_ENABLED(CONFIG_DEBUG_OBJECTS_RCU_HEAD)) { | |
| pr_alert("%s: !CONFIG_DEBUG_OBJECTS_RCU_HEAD, not testing duplicate call_%s()\n", | |
| KBUILD_MODNAME, cur_ops->name); | |
| return; | |
| } | |
| if (WARN_ON_ONCE(cur_ops->debug_objects && | |
| (!cur_ops->call || !cur_ops->cb_barrier))) | |
| return; | |
| struct rcu_head *rhp = kmalloc_obj(*rhp); | |
| init_rcu_head_on_stack(&rh1); | |
| init_rcu_head_on_stack(&rh2); | |
| pr_alert("%s: WARN: Duplicate call_%s() test starting.\n", KBUILD_MODNAME, cur_ops->name); | |
| /* Try to queue the rh2 pair of callbacks for the same grace period. */ | |
| idx = cur_ops->readlock(); /* Make it impossible to finish a grace period. */ | |
| cur_ops->call(&rh1, rcu_torture_leak_cb); /* Start grace period. */ | |
| cur_ops->call(&rh2, rcu_torture_leak_cb); | |
| cur_ops->call(&rh2, rcu_torture_err_cb); /* Duplicate callback. */ | |
| if (rhp) { | |
| cur_ops->call(rhp, rcu_torture_leak_cb); | |
| cur_ops->call(rhp, rcu_torture_err_cb); /* Another duplicate callback. */ | |
| } | |
| cur_ops->readunlock(idx); | |
| /* Wait for them all to get done so we can safely return. */ | |
| cur_ops->cb_barrier(); | |
| pr_alert("%s: WARN: Duplicate call_%s() test complete.\n", KBUILD_MODNAME, cur_ops->name); | |
| destroy_rcu_head_on_stack(&rh1); | |
| destroy_rcu_head_on_stack(&rh2); | |
| kfree(rhp); | |
| } | |
| static void rcutorture_sync(void) | |
| { | |
| static unsigned long n; | |
| if (cur_ops->sync && !(++n & 0xfff)) | |
| cur_ops->sync(); | |
| } | |
| static DEFINE_MUTEX(mut0); | |
| static DEFINE_MUTEX(mut1); | |
| static DEFINE_MUTEX(mut2); | |
| static DEFINE_MUTEX(mut3); | |
| static DEFINE_MUTEX(mut4); | |
| static DEFINE_MUTEX(mut5); | |
| static DEFINE_MUTEX(mut6); | |
| static DEFINE_MUTEX(mut7); | |
| static DEFINE_MUTEX(mut8); | |
| static DEFINE_MUTEX(mut9); | |
| static DECLARE_RWSEM(rwsem0); | |
| static DECLARE_RWSEM(rwsem1); | |
| static DECLARE_RWSEM(rwsem2); | |
| static DECLARE_RWSEM(rwsem3); | |
| static DECLARE_RWSEM(rwsem4); | |
| static DECLARE_RWSEM(rwsem5); | |
| static DECLARE_RWSEM(rwsem6); | |
| static DECLARE_RWSEM(rwsem7); | |
| static DECLARE_RWSEM(rwsem8); | |
| static DECLARE_RWSEM(rwsem9); | |
| DEFINE_STATIC_SRCU(srcu0); | |
| DEFINE_STATIC_SRCU(srcu1); | |
| DEFINE_STATIC_SRCU(srcu2); | |
| DEFINE_STATIC_SRCU(srcu3); | |
| DEFINE_STATIC_SRCU(srcu4); | |
| DEFINE_STATIC_SRCU(srcu5); | |
| DEFINE_STATIC_SRCU(srcu6); | |
| DEFINE_STATIC_SRCU(srcu7); | |
| DEFINE_STATIC_SRCU(srcu8); | |
| DEFINE_STATIC_SRCU(srcu9); | |
| static int srcu_lockdep_next(const char *f, const char *fl, const char *fs, const char *fu, int i, | |
| int cyclelen, int deadlock) | |
| { | |
| int j = i + 1; | |
| if (j >= cyclelen) | |
| j = deadlock ? 0 : -1; | |
| if (j >= 0) | |
| pr_info("%s: %s(%d), %s(%d), %s(%d)\n", f, fl, i, fs, j, fu, i); | |
| else | |
| pr_info("%s: %s(%d), %s(%d)\n", f, fl, i, fu, i); | |
| return j; | |
| } | |
| // Test lockdep on SRCU-based deadlock scenarios. | |
| static void rcu_torture_init_srcu_lockdep(void) | |
| { | |
| int cyclelen; | |
| int deadlock; | |
| bool err = false; | |
| int i; | |
| int j; | |
| int idx; | |
| struct mutex *muts[] = { &mut0, &mut1, &mut2, &mut3, &mut4, | |
| &mut5, &mut6, &mut7, &mut8, &mut9 }; | |
| struct rw_semaphore *rwsems[] = { &rwsem0, &rwsem1, &rwsem2, &rwsem3, &rwsem4, | |
| &rwsem5, &rwsem6, &rwsem7, &rwsem8, &rwsem9 }; | |
| struct srcu_struct *srcus[] = { &srcu0, &srcu1, &srcu2, &srcu3, &srcu4, | |
| &srcu5, &srcu6, &srcu7, &srcu8, &srcu9 }; | |
| int testtype; | |
| if (!test_srcu_lockdep) | |
| return; | |
| deadlock = test_srcu_lockdep / 1000; | |
| testtype = (test_srcu_lockdep / 10) % 100; | |
| cyclelen = test_srcu_lockdep % 10; | |
| WARN_ON_ONCE(ARRAY_SIZE(muts) != ARRAY_SIZE(srcus)); | |
| if (WARN_ONCE(deadlock != !!deadlock, | |
| "%s: test_srcu_lockdep=%d and deadlock digit %d must be zero or one.\n", | |
| __func__, test_srcu_lockdep, deadlock)) | |
| err = true; | |
| if (WARN_ONCE(cyclelen <= 0, | |
| "%s: test_srcu_lockdep=%d and cycle-length digit %d must be greater than zero.\n", | |
| __func__, test_srcu_lockdep, cyclelen)) | |
| err = true; | |
| if (err) | |
| goto err_out; | |
| if (testtype == 0) { | |
| pr_info("%s: test_srcu_lockdep = %05d: SRCU %d-way %sdeadlock.\n", | |
| __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); | |
| if (deadlock && cyclelen == 1) | |
| pr_info("%s: Expect hang.\n", __func__); | |
| for (i = 0; i < cyclelen; i++) { | |
| j = srcu_lockdep_next(__func__, "srcu_read_lock", "synchronize_srcu", | |
| "srcu_read_unlock", i, cyclelen, deadlock); | |
| idx = srcu_read_lock(srcus[i]); | |
| if (j >= 0) | |
| synchronize_srcu(srcus[j]); | |
| srcu_read_unlock(srcus[i], idx); | |
| } | |
| return; | |
| } | |
| if (testtype == 1) { | |
| pr_info("%s: test_srcu_lockdep = %05d: SRCU/mutex %d-way %sdeadlock.\n", | |
| __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); | |
| for (i = 0; i < cyclelen; i++) { | |
| pr_info("%s: srcu_read_lock(%d), mutex_lock(%d), mutex_unlock(%d), srcu_read_unlock(%d)\n", | |
| __func__, i, i, i, i); | |
| idx = srcu_read_lock(srcus[i]); | |
| mutex_lock(muts[i]); | |
| mutex_unlock(muts[i]); | |
| srcu_read_unlock(srcus[i], idx); | |
| j = srcu_lockdep_next(__func__, "mutex_lock", "synchronize_srcu", | |
| "mutex_unlock", i, cyclelen, deadlock); | |
| mutex_lock(muts[i]); | |
| if (j >= 0) | |
| synchronize_srcu(srcus[j]); | |
| mutex_unlock(muts[i]); | |
| } | |
| return; | |
| } | |
| if (testtype == 2) { | |
| pr_info("%s: test_srcu_lockdep = %05d: SRCU/rwsem %d-way %sdeadlock.\n", | |
| __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); | |
| for (i = 0; i < cyclelen; i++) { | |
| pr_info("%s: srcu_read_lock(%d), down_read(%d), up_read(%d), srcu_read_unlock(%d)\n", | |
| __func__, i, i, i, i); | |
| idx = srcu_read_lock(srcus[i]); | |
| down_read(rwsems[i]); | |
| up_read(rwsems[i]); | |
| srcu_read_unlock(srcus[i], idx); | |
| j = srcu_lockdep_next(__func__, "down_write", "synchronize_srcu", | |
| "up_write", i, cyclelen, deadlock); | |
| down_write(rwsems[i]); | |
| if (j >= 0) | |
| synchronize_srcu(srcus[j]); | |
| up_write(rwsems[i]); | |
| } | |
| return; | |
| } | |
| #ifdef CONFIG_TASKS_TRACE_RCU | |
| if (testtype == 3) { | |
| pr_info("%s: test_srcu_lockdep = %05d: SRCU and Tasks Trace RCU %d-way %sdeadlock.\n", | |
| __func__, test_srcu_lockdep, cyclelen, deadlock ? "" : "non-"); | |
| if (deadlock && cyclelen == 1) | |
| pr_info("%s: Expect hang.\n", __func__); | |
| for (i = 0; i < cyclelen; i++) { | |
| char *fl = i == 0 ? "rcu_read_lock_trace" : "srcu_read_lock"; | |
| char *fs = i == cyclelen - 1 ? "synchronize_rcu_tasks_trace" | |
| : "synchronize_srcu"; | |
| char *fu = i == 0 ? "rcu_read_unlock_trace" : "srcu_read_unlock"; | |
| j = srcu_lockdep_next(__func__, fl, fs, fu, i, cyclelen, deadlock); | |
| if (i == 0) | |
| rcu_read_lock_trace(); | |
| else | |
| idx = srcu_read_lock(srcus[i]); | |
| if (j >= 0) { | |
| if (i == cyclelen - 1) | |
| synchronize_rcu_tasks_trace(); | |
| else | |
| synchronize_srcu(srcus[j]); | |
| } | |
| if (i == 0) | |
| rcu_read_unlock_trace(); | |
| else | |
| srcu_read_unlock(srcus[i], idx); | |
| } | |
| return; | |
| } | |
| #endif // #ifdef CONFIG_TASKS_TRACE_RCU | |
| err_out: | |
| pr_info("%s: test_srcu_lockdep = %05d does nothing.\n", __func__, test_srcu_lockdep); | |
| pr_info("%s: test_srcu_lockdep = DNNL.\n", __func__); | |
| pr_info("%s: D: Deadlock if nonzero.\n", __func__); | |
| pr_info("%s: NN: Test number, 0=SRCU, 1=SRCU/mutex, 2=SRCU/rwsem, 3=SRCU/Tasks Trace RCU.\n", __func__); | |
| pr_info("%s: L: Cycle length.\n", __func__); | |
| if (!IS_ENABLED(CONFIG_TASKS_TRACE_RCU)) | |
| pr_info("%s: NN=3 disallowed because kernel is built with CONFIG_TASKS_TRACE_RCU=n\n", __func__); | |
| } | |
| 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, TRIVIAL_PREEMPT_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 (stall_only) { | |
| pr_alert("rcu-torture: stall_only specified, suppressing all else.\n"); | |
| fqs_stutter = 0; | |
| fwd_progress = 0; | |
| n_barrier_cbs = 0; | |
| nfakewriters = 0; | |
| nocbs_nthreads = 0; | |
| nreaders = 0; | |
| n_up_down = 0; | |
| nwriters = 0; | |
| onoff_interval = 0; | |
| preempt_duration = 0; | |
| read_exit_burst = 0; | |
| shuffle_interval = 0; | |
| stutter = 0; | |
| test_boost = 0; | |
| } | |
| 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 (nocbs_nthreads != 0 && (cur_ops != &rcu_ops || | |
| !IS_ENABLED(CONFIG_RCU_NOCB_CPU))) { | |
| pr_alert("rcu-torture types: %s and CONFIG_RCU_NOCB_CPU=%d, nocb toggle disabled.\n", | |
| cur_ops->name, IS_ENABLED(CONFIG_RCU_NOCB_CPU)); | |
| nocbs_nthreads = 0; | |
| } | |
| if (cur_ops->init) | |
| cur_ops->init(); | |
| rcu_torture_init_srcu_lockdep(); | |
| if (nfakewriters >= 0) { | |
| nrealfakewriters = nfakewriters; | |
| } else { | |
| nrealfakewriters = num_online_cpus() - 2 - nfakewriters; | |
| if (nrealfakewriters <= 0) | |
| nrealfakewriters = 1; | |
| } | |
| 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"); | |
| if (cur_ops->get_gp_data) | |
| cur_ops->get_gp_data(&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_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(); | |
| if (nrealfakewriters > 0) { | |
| fakewriter_tasks = kzalloc_objs(fakewriter_tasks[0], | |
| nrealfakewriters); | |
| if (fakewriter_tasks == NULL) { | |
| TOROUT_ERRSTRING("out of memory"); | |
| firsterr = -ENOMEM; | |
| goto unwind; | |
| } | |
| } | |
| for (i = 0; i < nrealfakewriters; i++) { | |
| firsterr = torture_create_kthread(rcu_torture_fakewriter, | |
| NULL, fakewriter_tasks[i]); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| reader_tasks = kzalloc_objs(reader_tasks[0], nrealreaders); | |
| rcu_torture_reader_mbchk = kzalloc_objs(*rcu_torture_reader_mbchk, | |
| nrealreaders); | |
| if (!reader_tasks || !rcu_torture_reader_mbchk) { | |
| 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 (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| if (nwriters) { | |
| firsterr = torture_create_kthread(rcu_torture_writer, NULL, writer_task); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| firsterr = rcu_torture_updown_init(); | |
| if (torture_init_error(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 = kzalloc_objs(nocb_tasks[0], nrealnocbers); | |
| if (nocb_tasks == NULL) { | |
| 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 (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| if (stat_interval > 0) { | |
| firsterr = torture_create_kthread(rcu_torture_stats, NULL, | |
| stats_task); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| if (test_no_idle_hz && shuffle_interval > 0) { | |
| firsterr = torture_shuffle_init(shuffle_interval * HZ); | |
| if (torture_init_error(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 (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| if (fqs_duration < 0) | |
| fqs_duration = 0; | |
| if (fqs_holdoff < 0) | |
| fqs_holdoff = 0; | |
| if (fqs_duration && fqs_holdoff) { | |
| /* Create the fqs thread */ | |
| firsterr = torture_create_kthread(rcu_torture_fqs, NULL, | |
| fqs_task); | |
| if (torture_init_error(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); | |
| rcutor_hp = firsterr; | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| shutdown_jiffies = jiffies + shutdown_secs * HZ; | |
| firsterr = torture_shutdown_init(shutdown_secs, rcu_torture_cleanup); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| firsterr = torture_onoff_init(onoff_holdoff * HZ, onoff_interval, | |
| rcutorture_sync); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| firsterr = rcu_torture_stall_init(); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| firsterr = rcu_torture_fwd_prog_init(); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| firsterr = rcu_torture_barrier_init(); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| firsterr = rcu_torture_read_exit_init(); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| if (preempt_duration > 0) { | |
| firsterr = torture_create_kthread(rcu_torture_preempt, NULL, preempt_task); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| if (object_debug) | |
| rcu_test_debug_objects(); | |
| if (cur_ops->gp_slow_register && !WARN_ON_ONCE(!cur_ops->gp_slow_unregister)) | |
| cur_ops->gp_slow_register(&rcu_fwd_cb_nodelay); | |
| if (gpwrap_lag && cur_ops->set_gpwrap_lag) { | |
| firsterr = rcu_gpwrap_lag_init(); | |
| if (torture_init_error(firsterr)) | |
| goto unwind; | |
| } | |
| 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); |