1117 lines · 29.5 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0+/* * Module-based torture test facility for locking * * Copyright (C) IBM Corporation, 2014 * * Authors: Paul E. McKenney <paulmck@linux.ibm.com> *          Davidlohr Bueso <dave@stgolabs.net> *	Based on kernel/rcu/torture.c. */ #define pr_fmt(fmt) fmt #include <linux/kernel.h>#include <linux/module.h>#include <linux/kthread.h>#include <linux/sched/rt.h>#include <linux/spinlock.h>#include <linux/mutex.h>#include <linux/rwsem.h>#include <linux/smp.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <uapi/linux/sched/types.h>#include <linux/rtmutex.h>#include <linux/atomic.h>#include <linux/moduleparam.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/torture.h>#include <linux/reboot.h> MODULE_LICENSE("GPL");MODULE_AUTHOR("Paul E. McKenney <paulmck@linux.ibm.com>"); torture_param(int, nwriters_stress, -1,	     "Number of write-locking stress-test threads");torture_param(int, nreaders_stress, -1,	     "Number of read-locking stress-test threads");torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");torture_param(int, onoff_interval, 0,	     "Time between CPU hotplugs (s), 0=disable");torture_param(int, shuffle_interval, 3,	     "Number of jiffies between shuffles, 0=disable");torture_param(int, shutdown_secs, 0, "Shutdown time (j), <= zero to disable.");torture_param(int, stat_interval, 60,	     "Number of seconds between stats printk()s");torture_param(int, stutter, 5, "Number of jiffies to run/halt test, 0=disable");torture_param(int, verbose, 1,	     "Enable verbose debugging printk()s"); static char *torture_type = "spin_lock";module_param(torture_type, charp, 0444);MODULE_PARM_DESC(torture_type,		 "Type of lock to torture (spin_lock, spin_lock_irq, mutex_lock, ...)"); static struct task_struct *stats_task;static struct task_struct **writer_tasks;static struct task_struct **reader_tasks; static bool lock_is_write_held;static atomic_t lock_is_read_held;static unsigned long last_lock_release; struct lock_stress_stats {	long n_lock_fail;	long n_lock_acquired;}; /* Forward reference. */static void lock_torture_cleanup(void); /* * Operations vector for selecting different types of tests. */struct lock_torture_ops {	void (*init)(void);	void (*exit)(void);	int (*writelock)(int tid);	void (*write_delay)(struct torture_random_state *trsp);	void (*task_boost)(struct torture_random_state *trsp);	void (*writeunlock)(int tid);	int (*readlock)(int tid);	void (*read_delay)(struct torture_random_state *trsp);	void (*readunlock)(int tid); 	unsigned long flags; /* for irq spinlocks */	const char *name;}; struct lock_torture_cxt {	int nrealwriters_stress;	int nrealreaders_stress;	bool debug_lock;	bool init_called;	atomic_t n_lock_torture_errors;	struct lock_torture_ops *cur_ops;	struct lock_stress_stats *lwsa; /* writer statistics */	struct lock_stress_stats *lrsa; /* reader statistics */};static struct lock_torture_cxt cxt = { 0, 0, false, false,				       ATOMIC_INIT(0),				       NULL, NULL};/* * Definitions for lock torture testing. */ static int torture_lock_busted_write_lock(int tid __maybe_unused){	return 0;  /* BUGGY, do not use in real life!!! */} static void torture_lock_busted_write_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		torture_preempt_schedule();  /* Allow test to be preempted. */} static void torture_lock_busted_write_unlock(int tid __maybe_unused){	  /* BUGGY, do not use in real life!!! */} static void torture_boost_dummy(struct torture_random_state *trsp){	/* Only rtmutexes care about priority */} static struct lock_torture_ops lock_busted_ops = {	.writelock	= torture_lock_busted_write_lock,	.write_delay	= torture_lock_busted_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_lock_busted_write_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "lock_busted"}; static DEFINE_SPINLOCK(torture_spinlock); static int torture_spin_lock_write_lock(int tid __maybe_unused)__acquires(torture_spinlock){	spin_lock(&torture_spinlock);	return 0;} static void torture_spin_lock_write_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 2;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2 * shortdelay_us)))		udelay(shortdelay_us);	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		torture_preempt_schedule();  /* Allow test to be preempted. */} static void torture_spin_lock_write_unlock(int tid __maybe_unused)__releases(torture_spinlock){	spin_unlock(&torture_spinlock);} static struct lock_torture_ops spin_lock_ops = {	.writelock	= torture_spin_lock_write_lock,	.write_delay	= torture_spin_lock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_spin_lock_write_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "spin_lock"}; static int torture_spin_lock_write_lock_irq(int tid __maybe_unused)__acquires(torture_spinlock){	unsigned long flags; 	spin_lock_irqsave(&torture_spinlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_lock_spin_write_unlock_irq(int tid __maybe_unused)__releases(torture_spinlock){	spin_unlock_irqrestore(&torture_spinlock, cxt.cur_ops->flags);} static struct lock_torture_ops spin_lock_irq_ops = {	.writelock	= torture_spin_lock_write_lock_irq,	.write_delay	= torture_spin_lock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_lock_spin_write_unlock_irq,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "spin_lock_irq"}; static DEFINE_RWLOCK(torture_rwlock); static int torture_rwlock_write_lock(int tid __maybe_unused)__acquires(torture_rwlock){	write_lock(&torture_rwlock);	return 0;} static void torture_rwlock_write_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 2;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	else		udelay(shortdelay_us);} static void torture_rwlock_write_unlock(int tid __maybe_unused)__releases(torture_rwlock){	write_unlock(&torture_rwlock);} static int torture_rwlock_read_lock(int tid __maybe_unused)__acquires(torture_rwlock){	read_lock(&torture_rwlock);	return 0;} static void torture_rwlock_read_delay(struct torture_random_state *trsp){	const unsigned long shortdelay_us = 10;	const unsigned long longdelay_ms = 100; 	/* We want a short delay mostly to emulate likely code, and	 * we want a long delay occasionally to force massive contention.	 */	if (!(torture_random(trsp) %	      (cxt.nrealreaders_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms);	else		udelay(shortdelay_us);} static void torture_rwlock_read_unlock(int tid __maybe_unused)__releases(torture_rwlock){	read_unlock(&torture_rwlock);} static struct lock_torture_ops rw_lock_ops = {	.writelock	= torture_rwlock_write_lock,	.write_delay	= torture_rwlock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_rwlock_write_unlock,	.readlock       = torture_rwlock_read_lock,	.read_delay     = torture_rwlock_read_delay,	.readunlock     = torture_rwlock_read_unlock,	.name		= "rw_lock"}; static int torture_rwlock_write_lock_irq(int tid __maybe_unused)__acquires(torture_rwlock){	unsigned long flags; 	write_lock_irqsave(&torture_rwlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_rwlock_write_unlock_irq(int tid __maybe_unused)__releases(torture_rwlock){	write_unlock_irqrestore(&torture_rwlock, cxt.cur_ops->flags);} static int torture_rwlock_read_lock_irq(int tid __maybe_unused)__acquires(torture_rwlock){	unsigned long flags; 	read_lock_irqsave(&torture_rwlock, flags);	cxt.cur_ops->flags = flags;	return 0;} static void torture_rwlock_read_unlock_irq(int tid __maybe_unused)__releases(torture_rwlock){	read_unlock_irqrestore(&torture_rwlock, cxt.cur_ops->flags);} static struct lock_torture_ops rw_lock_irq_ops = {	.writelock	= torture_rwlock_write_lock_irq,	.write_delay	= torture_rwlock_write_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_rwlock_write_unlock_irq,	.readlock       = torture_rwlock_read_lock_irq,	.read_delay     = torture_rwlock_read_delay,	.readunlock     = torture_rwlock_read_unlock_irq,	.name		= "rw_lock_irq"}; static DEFINE_MUTEX(torture_mutex); static int torture_mutex_lock(int tid __maybe_unused)__acquires(torture_mutex){	mutex_lock(&torture_mutex);	return 0;} static void torture_mutex_delay(struct torture_random_state *trsp){	const unsigned long longdelay_ms = 100; 	/* We want a long delay occasionally to force massive contention.  */	if (!(torture_random(trsp) %	      (cxt.nrealwriters_stress * 2000 * longdelay_ms)))		mdelay(longdelay_ms * 5);	else		mdelay(longdelay_ms / 5);	if (!(torture_random(trsp) % (cxt.nrealwriters_stress * 20000)))		torture_preempt_schedule();  /* Allow test to be preempted. */} static void torture_mutex_unlock(int tid __maybe_unused)__releases(torture_mutex){	mutex_unlock(&torture_mutex);} static struct lock_torture_ops mutex_lock_ops = {	.writelock	= torture_mutex_lock,	.write_delay	= torture_mutex_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_mutex_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "mutex_lock"}; #include <linux/ww_mutex.h>/* * The torture ww_mutexes should belong to the same lock class as * torture_ww_class to avoid lockdep problem. The ww_mutex_init() * function is called for initialization to ensure that. */static DEFINE_WD_CLASS(torture_ww_class);static struct ww_mutex torture_ww_mutex_0, torture_ww_mutex_1, torture_ww_mutex_2;static struct ww_acquire_ctx *ww_acquire_ctxs; static void torture_ww_mutex_init(void){	ww_mutex_init(&torture_ww_mutex_0, &torture_ww_class);	ww_mutex_init(&torture_ww_mutex_1, &torture_ww_class);	ww_mutex_init(&torture_ww_mutex_2, &torture_ww_class); 	ww_acquire_ctxs = kmalloc_array(cxt.nrealwriters_stress,					sizeof(*ww_acquire_ctxs),					GFP_KERNEL);	if (!ww_acquire_ctxs)		VERBOSE_TOROUT_STRING("ww_acquire_ctx: Out of memory");} static void torture_ww_mutex_exit(void){	kfree(ww_acquire_ctxs);} static int torture_ww_mutex_lock(int tid)__acquires(torture_ww_mutex_0)__acquires(torture_ww_mutex_1)__acquires(torture_ww_mutex_2){	LIST_HEAD(list);	struct reorder_lock {		struct list_head link;		struct ww_mutex *lock;	} locks[3], *ll, *ln;	struct ww_acquire_ctx *ctx = &ww_acquire_ctxs[tid]; 	locks[0].lock = &torture_ww_mutex_0;	list_add(&locks[0].link, &list); 	locks[1].lock = &torture_ww_mutex_1;	list_add(&locks[1].link, &list); 	locks[2].lock = &torture_ww_mutex_2;	list_add(&locks[2].link, &list); 	ww_acquire_init(ctx, &torture_ww_class); 	list_for_each_entry(ll, &list, link) {		int err; 		err = ww_mutex_lock(ll->lock, ctx);		if (!err)			continue; 		ln = ll;		list_for_each_entry_continue_reverse(ln, &list, link)			ww_mutex_unlock(ln->lock); 		if (err != -EDEADLK)			return err; 		ww_mutex_lock_slow(ll->lock, ctx);		list_move(&ll->link, &list);	} 	return 0;} static void torture_ww_mutex_unlock(int tid)__releases(torture_ww_mutex_0)__releases(torture_ww_mutex_1)__releases(torture_ww_mutex_2){	struct ww_acquire_ctx *ctx = &ww_acquire_ctxs[tid]; 	ww_mutex_unlock(&torture_ww_mutex_0);	ww_mutex_unlock(&torture_ww_mutex_1);	ww_mutex_unlock(&torture_ww_mutex_2);	ww_acquire_fini(ctx);} static struct lock_torture_ops ww_mutex_lock_ops = {	.init		= torture_ww_mutex_init,	.exit		= torture_ww_mutex_exit,	.writelock	= torture_ww_mutex_lock,	.write_delay	= torture_mutex_delay,	.task_boost     = torture_boost_dummy,	.writeunlock	= torture_ww_mutex_unlock,	.readlock       = NULL,	.read_delay     = NULL,	.readunlock     = NULL,	.name		= "ww_mutex_lock"}; #ifdef CONFIG_RT_MUTEXESstatic DEFINE_RT_MUTEX(torture_rtmutex); static int torture_rtmutex_lock(int tid __maybe_unused)__acquires(torture_rtmutex){	rt_mutex_lock(&torture_rtmutex);	return 0;} static void torture_rtmutex_boost(struct torture_random_state *trsp){	const unsigned int factor = 50000; /* yes, quite arbitrary */ 	if (!rt_task(current)) {		/*		 * Boost priority once every ~50k operations. When the		 * task tries to take the lock, the rtmutex it will account		 * for the new priority, and do any corresponding pi-dance.		 */		if (trsp && !(torture_random(trsp) %			      (cxt.nrealwriters_stress * factor))) {			sched_set_fifo(current);		} else /* common case, do nothing */			return;	} else {		/*		 * The task will remain boosted for another ~500k operations,		 * then restored back to its original prio, and so forth.		 *		 * When @trsp is nil, we want to force-reset the task for		 * stopping the kthread.		 */		if (!trsp || !(torture_random(trsp) %

Lines 1–500 of 1117.