Commit ad56450d authored by Boqun Feng's avatar Boqun Feng Committed by Peter Zijlstra
Browse files

locking/selftest: Add test cases for queued_read_lock()



Add two self test cases for the following case:

	P0:			P1:			P2:

				<in irq handler>
	spin_lock_irq(&slock)	read_lock(&rwlock)
							write_lock_irq(&rwlock)
	read_lock(&rwlock)	spin_lock(&slock)

, which is a deadlock, as the read_lock() on P0 cannot get the lock
because of the fairness.

	P0:			P1:			P2:

	<in irq handler>
	spin_lock(&slock)	read_lock(&rwlock)
							write_lock(&rwlock)
	read_lock(&rwlock)	spin_lock_irq(&slock)

, which is not a deadlock, as the read_lock() on P0 can get the lock
because it could use the unfair fastpass.

Signed-off-by: default avatarBoqun Feng <boqun.feng@gmail.com>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lkml.kernel.org/r/20200807074238.1632519-19-boqun.feng@gmail.com
parent 108dc42e
Loading
Loading
Loading
Loading
+104 −0
Original line number Diff line number Diff line
@@ -2201,6 +2201,108 @@ static void ww_tests(void)
	pr_cont("\n");
}


/*
 * <in hardirq handler>
 * read_lock(&A);
 *			<hardirq disable>
 *			spin_lock(&B);
 * spin_lock(&B);
 *			read_lock(&A);
 *
 * is a deadlock.
 */
static void queued_read_lock_hardirq_RE_Er(void)
{
	HARDIRQ_ENTER();
	read_lock(&rwlock_A);
	LOCK(B);
	UNLOCK(B);
	read_unlock(&rwlock_A);
	HARDIRQ_EXIT();

	HARDIRQ_DISABLE();
	LOCK(B);
	read_lock(&rwlock_A);
	read_unlock(&rwlock_A);
	UNLOCK(B);
	HARDIRQ_ENABLE();
}

/*
 * <in hardirq handler>
 * spin_lock(&B);
 *			<hardirq disable>
 *			read_lock(&A);
 * read_lock(&A);
 *			spin_lock(&B);
 *
 * is not a deadlock.
 */
static void queued_read_lock_hardirq_ER_rE(void)
{
	HARDIRQ_ENTER();
	LOCK(B);
	read_lock(&rwlock_A);
	read_unlock(&rwlock_A);
	UNLOCK(B);
	HARDIRQ_EXIT();

	HARDIRQ_DISABLE();
	read_lock(&rwlock_A);
	LOCK(B);
	UNLOCK(B);
	read_unlock(&rwlock_A);
	HARDIRQ_ENABLE();
}

/*
 * <hardirq disable>
 * spin_lock(&B);
 *			read_lock(&A);
 *			<in hardirq handler>
 *			spin_lock(&B);
 * read_lock(&A);
 *
 * is a deadlock. Because the two read_lock()s are both non-recursive readers.
 */
static void queued_read_lock_hardirq_inversion(void)
{

	HARDIRQ_ENTER();
	LOCK(B);
	UNLOCK(B);
	HARDIRQ_EXIT();

	HARDIRQ_DISABLE();
	LOCK(B);
	read_lock(&rwlock_A);
	read_unlock(&rwlock_A);
	UNLOCK(B);
	HARDIRQ_ENABLE();

	read_lock(&rwlock_A);
	read_unlock(&rwlock_A);
}

static void queued_read_lock_tests(void)
{
	printk("  --------------------------------------------------------------------------\n");
	printk("  | queued read lock tests |\n");
	printk("  ---------------------------\n");
	print_testname("hardirq read-lock/lock-read");
	dotest(queued_read_lock_hardirq_RE_Er, FAILURE, LOCKTYPE_RWLOCK);
	pr_cont("\n");

	print_testname("hardirq lock-read/read-lock");
	dotest(queued_read_lock_hardirq_ER_rE, SUCCESS, LOCKTYPE_RWLOCK);
	pr_cont("\n");

	print_testname("hardirq inversion");
	dotest(queued_read_lock_hardirq_inversion, FAILURE, LOCKTYPE_RWLOCK);
	pr_cont("\n");
}

void locking_selftest(void)
{
	/*
@@ -2318,6 +2420,8 @@ void locking_selftest(void)
	/*
	 * queued_read_lock() specific test cases can be put here
	 */
	if (IS_ENABLED(CONFIG_QUEUED_RWLOCKS))
		queued_read_lock_tests();

	if (unexpected_testcase_failures) {
		printk("-----------------------------------------------------------------\n");