1051 lines · 27.7 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/rwlock.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/percpu-rwsem.h>#include <linux/torture.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 bool lock_is_read_held; 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);	int (*writelock)(void);	void (*write_delay)(struct torture_random_state *trsp);	void (*task_boost)(struct torture_random_state *trsp);	void (*writeunlock)(void);	int (*readlock)(void);	void (*read_delay)(struct torture_random_state *trsp);	void (*readunlock)(void); 	unsigned long flags; /* for irq spinlocks */	const char *name;}; struct lock_torture_cxt {	int nrealwriters_stress;	int nrealreaders_stress;	bool debug_lock;	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,				       ATOMIC_INIT(0),				       NULL, NULL};/* * Definitions for lock torture testing. */ static int torture_lock_busted_write_lock(void){	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(void){	  /* 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(void) __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(void) __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(void)__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(void)__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(void) __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(void) __releases(torture_rwlock){	write_unlock(&torture_rwlock);} static int torture_rwlock_read_lock(void) __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(void) __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(void) __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(void)__releases(torture_rwlock){	write_unlock_irqrestore(&torture_rwlock, cxt.cur_ops->flags);} static int torture_rwlock_read_lock_irq(void) __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(void)__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(void) __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(void) __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>static DEFINE_WD_CLASS(torture_ww_class);static DEFINE_WW_MUTEX(torture_ww_mutex_0, &torture_ww_class);static DEFINE_WW_MUTEX(torture_ww_mutex_1, &torture_ww_class);static DEFINE_WW_MUTEX(torture_ww_mutex_2, &torture_ww_class); static int torture_ww_mutex_lock(void)__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; 	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);	} 	ww_acquire_fini(&ctx);	return 0;} static void torture_ww_mutex_unlock(void)__releases(torture_ww_mutex_0)__releases(torture_ww_mutex_1)__releases(torture_ww_mutex_2){	ww_mutex_unlock(&torture_ww_mutex_0);	ww_mutex_unlock(&torture_ww_mutex_1);	ww_mutex_unlock(&torture_ww_mutex_2);} static struct lock_torture_ops ww_mutex_lock_ops = {	.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(void) __acquires(torture_rtmutex){	rt_mutex_lock(&torture_rtmutex);	return 0;} static void torture_rtmutex_boost(struct torture_random_state *trsp){	int policy;	struct sched_param param;	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))) {			policy = SCHED_FIFO;			param.sched_priority = MAX_RT_PRIO - 1;		} 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) %			       (cxt.nrealwriters_stress * factor * 2))) {			policy = SCHED_NORMAL;			param.sched_priority = 0;		} else /* common case, do nothing */			return;	} 	sched_setscheduler_nocheck(current, policy, &param);} static void torture_rtmutex_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_rtmutex_unlock(void) __releases(torture_rtmutex){	rt_mutex_unlock(&torture_rtmutex);} static struct lock_torture_ops rtmutex_lock_ops = {	.writelock	= torture_rtmutex_lock,

Lines 1–500 of 1051.