mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-12-28 11:59:34 +03:00
a6ae97caed
This implementation of rw_tryupgrade() behaves slightly differently from its counterparts on other platforms. It drops the RW_READER lock and then acquires the RW_WRITER lock leaving a small window where no lock is held. On other platforms the lock is never released during the upgrade process. This is necessary under Linux because the kernel does not provide an upgrade function. There are currently no callers in the ZFS code where this change in behavior is a problem. In fact, in most cases the code is already written such that if the upgrade fails the RW_READER lock is dropped and the caller blocks waiting to acquire the lock as RW_WRITER. Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov> Signed-off-by: Tim Chase <tim@chase2k.com> Signed-off-by: Matthew Thode <prometheanfire@gentoo.org> Closes zfsonlinux/zfs#4388 Closes #534
670 lines
19 KiB
C
670 lines
19 KiB
C
/*****************************************************************************\
|
|
* Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC.
|
|
* Copyright (C) 2007 The Regents of the University of California.
|
|
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
|
|
* Written by Brian Behlendorf <behlendorf1@llnl.gov>.
|
|
* UCRL-CODE-235197
|
|
*
|
|
* This file is part of the SPL, Solaris Porting Layer.
|
|
* For details, see <http://zfsonlinux.org/>.
|
|
*
|
|
* The SPL is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* The SPL is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with the SPL. If not, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************
|
|
* Solaris Porting LAyer Tests (SPLAT) Read/Writer Lock Tests.
|
|
\*****************************************************************************/
|
|
|
|
#include <sys/random.h>
|
|
#include <sys/rwlock.h>
|
|
#include <sys/taskq.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/mm_compat.h>
|
|
#include "splat-internal.h"
|
|
|
|
#define SPLAT_RWLOCK_NAME "rwlock"
|
|
#define SPLAT_RWLOCK_DESC "Kernel RW Lock Tests"
|
|
|
|
#define SPLAT_RWLOCK_TEST1_ID 0x0701
|
|
#define SPLAT_RWLOCK_TEST1_NAME "N-rd/1-wr"
|
|
#define SPLAT_RWLOCK_TEST1_DESC "Multiple readers one writer"
|
|
|
|
#define SPLAT_RWLOCK_TEST2_ID 0x0702
|
|
#define SPLAT_RWLOCK_TEST2_NAME "0-rd/N-wr"
|
|
#define SPLAT_RWLOCK_TEST2_DESC "Multiple writers"
|
|
|
|
#define SPLAT_RWLOCK_TEST3_ID 0x0703
|
|
#define SPLAT_RWLOCK_TEST3_NAME "held"
|
|
#define SPLAT_RWLOCK_TEST3_DESC "RW_{LOCK|READ|WRITE}_HELD"
|
|
|
|
#define SPLAT_RWLOCK_TEST4_ID 0x0704
|
|
#define SPLAT_RWLOCK_TEST4_NAME "tryenter"
|
|
#define SPLAT_RWLOCK_TEST4_DESC "Tryenter"
|
|
|
|
#define SPLAT_RWLOCK_TEST5_ID 0x0705
|
|
#define SPLAT_RWLOCK_TEST5_NAME "rw_downgrade"
|
|
#define SPLAT_RWLOCK_TEST5_DESC "Write downgrade"
|
|
|
|
#define SPLAT_RWLOCK_TEST6_ID 0x0706
|
|
#define SPLAT_RWLOCK_TEST6_NAME "rw_tryupgrade"
|
|
#define SPLAT_RWLOCK_TEST6_DESC "Read upgrade"
|
|
|
|
#define SPLAT_RWLOCK_TEST_MAGIC 0x115599DDUL
|
|
#define SPLAT_RWLOCK_TEST_NAME "rwlock_test"
|
|
#define SPLAT_RWLOCK_TEST_TASKQ "rwlock_taskq"
|
|
#define SPLAT_RWLOCK_TEST_COUNT 8
|
|
|
|
#define SPLAT_RWLOCK_RELEASE_INIT 0
|
|
#define SPLAT_RWLOCK_RELEASE_WR 1
|
|
#define SPLAT_RWLOCK_RELEASE_RD 2
|
|
|
|
typedef struct rw_priv {
|
|
unsigned long rw_magic;
|
|
struct file *rw_file;
|
|
krwlock_t rw_rwlock;
|
|
spinlock_t rw_lock;
|
|
wait_queue_head_t rw_waitq;
|
|
int rw_completed;
|
|
int rw_holders;
|
|
int rw_waiters;
|
|
int rw_release;
|
|
int rw_rc;
|
|
krw_t rw_type;
|
|
} rw_priv_t;
|
|
|
|
typedef struct rw_thr {
|
|
const char *rwt_name;
|
|
rw_priv_t *rwt_rwp;
|
|
struct task_struct *rwt_thread;
|
|
} rw_thr_t;
|
|
|
|
void splat_init_rw_priv(rw_priv_t *rwp, struct file *file)
|
|
{
|
|
rwp->rw_magic = SPLAT_RWLOCK_TEST_MAGIC;
|
|
rwp->rw_file = file;
|
|
rw_init(&rwp->rw_rwlock, SPLAT_RWLOCK_TEST_NAME, RW_DEFAULT, NULL);
|
|
spin_lock_init(&rwp->rw_lock);
|
|
init_waitqueue_head(&rwp->rw_waitq);
|
|
rwp->rw_completed = 0;
|
|
rwp->rw_holders = 0;
|
|
rwp->rw_waiters = 0;
|
|
rwp->rw_release = SPLAT_RWLOCK_RELEASE_INIT;
|
|
rwp->rw_rc = 0;
|
|
rwp->rw_type = 0;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_wr_thr(void *arg)
|
|
{
|
|
rw_thr_t *rwt = (rw_thr_t *)arg;
|
|
rw_priv_t *rwp = rwt->rwt_rwp;
|
|
uint8_t rnd;
|
|
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
|
|
get_random_bytes((void *)&rnd, 1);
|
|
msleep((unsigned int)rnd);
|
|
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s trying to acquire rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_waiters++;
|
|
spin_unlock(&rwp->rw_lock);
|
|
rw_enter(&rwp->rw_rwlock, RW_WRITER);
|
|
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_waiters--;
|
|
rwp->rw_holders++;
|
|
spin_unlock(&rwp->rw_lock);
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s acquired rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
|
|
/* Wait for control thread to signal we can release the write lock */
|
|
wait_event_interruptible(rwp->rw_waitq, splat_locked_test(&rwp->rw_lock,
|
|
rwp->rw_release == SPLAT_RWLOCK_RELEASE_WR));
|
|
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_completed++;
|
|
rwp->rw_holders--;
|
|
spin_unlock(&rwp->rw_lock);
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s dropped rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
|
|
rw_exit(&rwp->rw_rwlock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_rd_thr(void *arg)
|
|
{
|
|
rw_thr_t *rwt = (rw_thr_t *)arg;
|
|
rw_priv_t *rwp = rwt->rwt_rwp;
|
|
uint8_t rnd;
|
|
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
|
|
get_random_bytes((void *)&rnd, 1);
|
|
msleep((unsigned int)rnd);
|
|
|
|
/* Don't try and take the semaphore until after someone has it */
|
|
wait_event_interruptible(rwp->rw_waitq,
|
|
splat_locked_test(&rwp->rw_lock, rwp->rw_holders > 0));
|
|
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s trying to acquire rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_waiters++;
|
|
spin_unlock(&rwp->rw_lock);
|
|
rw_enter(&rwp->rw_rwlock, RW_READER);
|
|
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_waiters--;
|
|
rwp->rw_holders++;
|
|
spin_unlock(&rwp->rw_lock);
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s acquired rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
|
|
/* Wait for control thread to signal we can release the read lock */
|
|
wait_event_interruptible(rwp->rw_waitq, splat_locked_test(&rwp->rw_lock,
|
|
rwp->rw_release == SPLAT_RWLOCK_RELEASE_RD));
|
|
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_completed++;
|
|
rwp->rw_holders--;
|
|
spin_unlock(&rwp->rw_lock);
|
|
splat_vprint(rwp->rw_file, rwt->rwt_name,
|
|
"%s dropped rwlock (%d holding/%d waiting)\n",
|
|
rwt->rwt_thread->comm, rwp->rw_holders, rwp->rw_waiters);
|
|
|
|
rw_exit(&rwp->rw_rwlock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test1(struct file *file, void *arg)
|
|
{
|
|
int i, count = 0, rc = 0;
|
|
rw_thr_t rwt[SPLAT_RWLOCK_TEST_COUNT];
|
|
rw_priv_t *rwp;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
/* Create some threads, the exact number isn't important just as
|
|
* long as we know how many we managed to create and should expect. */
|
|
for (i = 0; i < SPLAT_RWLOCK_TEST_COUNT; i++) {
|
|
rwt[i].rwt_rwp = rwp;
|
|
rwt[i].rwt_name = SPLAT_RWLOCK_TEST1_NAME;
|
|
|
|
/* The first thread will be the writer */
|
|
if (i == 0)
|
|
rwt[i].rwt_thread = spl_kthread_create(splat_rwlock_wr_thr,
|
|
&rwt[i], "%s/%d", SPLAT_RWLOCK_TEST_NAME, i);
|
|
else
|
|
rwt[i].rwt_thread = spl_kthread_create(splat_rwlock_rd_thr,
|
|
&rwt[i], "%s/%d", SPLAT_RWLOCK_TEST_NAME, i);
|
|
|
|
if (!IS_ERR(rwt[i].rwt_thread)) {
|
|
wake_up_process(rwt[i].rwt_thread);
|
|
count++;
|
|
}
|
|
}
|
|
|
|
/* Wait for the writer */
|
|
while (splat_locked_test(&rwp->rw_lock, rwp->rw_holders == 0)) {
|
|
wake_up_interruptible(&rwp->rw_waitq);
|
|
msleep(100);
|
|
}
|
|
|
|
/* Wait for 'count-1' readers */
|
|
while (splat_locked_test(&rwp->rw_lock, rwp->rw_waiters < count - 1)) {
|
|
wake_up_interruptible(&rwp->rw_waitq);
|
|
msleep(100);
|
|
}
|
|
|
|
/* Verify there is only one lock holder */
|
|
if (splat_locked_test(&rwp->rw_lock, rwp->rw_holders) != 1) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST1_NAME, "Only 1 holder "
|
|
"expected for rwlock (%d holding/%d waiting)\n",
|
|
rwp->rw_holders, rwp->rw_waiters);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
/* Verify 'count-1' readers */
|
|
if (splat_locked_test(&rwp->rw_lock, rwp->rw_waiters != count - 1)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST1_NAME, "Only %d waiters "
|
|
"expected for rwlock (%d holding/%d waiting)\n",
|
|
count - 1, rwp->rw_holders, rwp->rw_waiters);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
/* Signal the writer to release, allows readers to acquire */
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_release = SPLAT_RWLOCK_RELEASE_WR;
|
|
wake_up_interruptible(&rwp->rw_waitq);
|
|
spin_unlock(&rwp->rw_lock);
|
|
|
|
/* Wait for 'count-1' readers to hold the lock */
|
|
while (splat_locked_test(&rwp->rw_lock, rwp->rw_holders < count - 1)) {
|
|
wake_up_interruptible(&rwp->rw_waitq);
|
|
msleep(100);
|
|
}
|
|
|
|
/* Verify there are 'count-1' readers */
|
|
if (splat_locked_test(&rwp->rw_lock, rwp->rw_holders != count - 1)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST1_NAME, "Only %d holders "
|
|
"expected for rwlock (%d holding/%d waiting)\n",
|
|
count - 1, rwp->rw_holders, rwp->rw_waiters);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
/* Release 'count-1' readers */
|
|
spin_lock(&rwp->rw_lock);
|
|
rwp->rw_release = SPLAT_RWLOCK_RELEASE_RD;
|
|
wake_up_interruptible(&rwp->rw_waitq);
|
|
spin_unlock(&rwp->rw_lock);
|
|
|
|
/* Wait for the test to complete */
|
|
while (splat_locked_test(&rwp->rw_lock,
|
|
rwp->rw_holders>0 || rwp->rw_waiters>0))
|
|
msleep(100);
|
|
|
|
rw_destroy(&(rwp->rw_rwlock));
|
|
kfree(rwp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void
|
|
splat_rwlock_test2_func(void *arg)
|
|
{
|
|
rw_priv_t *rwp = (rw_priv_t *)arg;
|
|
int rc;
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
|
|
/* Read the value before sleeping and write it after we wake up to
|
|
* maximize the chance of a race if rwlocks are not working properly */
|
|
rw_enter(&rwp->rw_rwlock, RW_WRITER);
|
|
rc = rwp->rw_rc;
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
schedule_timeout(HZ / 100); /* 1/100 of a second */
|
|
VERIFY(rwp->rw_rc == rc);
|
|
rwp->rw_rc = rc + 1;
|
|
rw_exit(&rwp->rw_rwlock);
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test2(struct file *file, void *arg)
|
|
{
|
|
rw_priv_t *rwp;
|
|
taskq_t *tq;
|
|
int i, rc = 0, tq_count = 256;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
/* Create several threads allowing tasks to race with each other */
|
|
tq = taskq_create(SPLAT_RWLOCK_TEST_TASKQ, num_online_cpus(),
|
|
defclsyspri, 50, INT_MAX, TASKQ_PREPOPULATE);
|
|
if (tq == NULL) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Schedule N work items to the work queue each of which enters the
|
|
* writer rwlock, sleeps briefly, then exits the writer rwlock. On a
|
|
* multiprocessor box these work items will be handled by all available
|
|
* CPUs. The task function checks to ensure the tracked shared variable
|
|
* is always only incremented by one. Additionally, the rwlock itself
|
|
* is instrumented such that if any two processors are in the
|
|
* critical region at the same time the system will panic. If the
|
|
* rwlock is implemented right this will never happy, that's a pass.
|
|
*/
|
|
for (i = 0; i < tq_count; i++) {
|
|
if (!taskq_dispatch(tq,splat_rwlock_test2_func,rwp,TQ_SLEEP)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST2_NAME,
|
|
"Failed to queue task %d\n", i);
|
|
rc = -EINVAL;
|
|
}
|
|
}
|
|
|
|
taskq_wait(tq);
|
|
|
|
if (rwp->rw_rc == tq_count) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST2_NAME, "%d racing threads "
|
|
"correctly entered/exited the rwlock %d times\n",
|
|
num_online_cpus(), rwp->rw_rc);
|
|
} else {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST2_NAME, "%d racing threads "
|
|
"only processed %d/%d w rwlock work items\n",
|
|
num_online_cpus(), rwp->rw_rc, tq_count);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
taskq_destroy(tq);
|
|
rw_destroy(&(rwp->rw_rwlock));
|
|
out:
|
|
kfree(rwp);
|
|
return rc;
|
|
}
|
|
|
|
#define splat_rwlock_test3_helper(rwp,rex1,rex2,wex1,wex2,held_func,rc) \
|
|
do { \
|
|
int result, _rc1_, _rc2_, _rc3_, _rc4_; \
|
|
\
|
|
rc = 0; \
|
|
rw_enter(&(rwp)->rw_rwlock, RW_READER); \
|
|
_rc1_ = ((result = held_func(&(rwp)->rw_rwlock)) != rex1); \
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST3_NAME, "%s" #held_func \
|
|
" returned %d (expected %d) when RW_READER\n", \
|
|
_rc1_ ? "Fail " : "", result, rex1); \
|
|
rw_exit(&(rwp)->rw_rwlock); \
|
|
_rc2_ = ((result = held_func(&(rwp)->rw_rwlock)) != rex2); \
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST3_NAME, "%s" #held_func \
|
|
" returned %d (expected %d) when !RW_READER\n", \
|
|
_rc2_ ? "Fail " : "", result, rex2); \
|
|
\
|
|
rw_enter(&(rwp)->rw_rwlock, RW_WRITER); \
|
|
_rc3_ = ((result = held_func(&(rwp)->rw_rwlock)) != wex1); \
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST3_NAME, "%s" #held_func \
|
|
" returned %d (expected %d) when RW_WRITER\n", \
|
|
_rc3_ ? "Fail " : "", result, wex1); \
|
|
rw_exit(&(rwp)->rw_rwlock); \
|
|
_rc4_ = ((result = held_func(&(rwp)->rw_rwlock)) != wex2); \
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST3_NAME, "%s" #held_func \
|
|
" returned %d (expected %d) when !RW_WRITER\n", \
|
|
_rc4_ ? "Fail " : "", result, wex2); \
|
|
\
|
|
rc = ((_rc1_ || _rc2_ || _rc3_ || _rc4_) ? -EINVAL : 0); \
|
|
} while(0);
|
|
|
|
static int
|
|
splat_rwlock_test3(struct file *file, void *arg)
|
|
{
|
|
rw_priv_t *rwp;
|
|
int rc1, rc2, rc3;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
splat_rwlock_test3_helper(rwp, 1, 0, 1, 0, RW_LOCK_HELD, rc1);
|
|
splat_rwlock_test3_helper(rwp, 1, 0, 0, 0, RW_READ_HELD, rc2);
|
|
splat_rwlock_test3_helper(rwp, 0, 0, 1, 0, RW_WRITE_HELD, rc3);
|
|
|
|
rw_destroy(&rwp->rw_rwlock);
|
|
kfree(rwp);
|
|
|
|
return ((rc1 || rc2 || rc3) ? -EINVAL : 0);
|
|
}
|
|
|
|
static void
|
|
splat_rwlock_test4_func(void *arg)
|
|
{
|
|
rw_priv_t *rwp = (rw_priv_t *)arg;
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
|
|
if (rw_tryenter(&rwp->rw_rwlock, rwp->rw_type)) {
|
|
rwp->rw_rc = 0;
|
|
rw_exit(&rwp->rw_rwlock);
|
|
} else {
|
|
rwp->rw_rc = -EBUSY;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
splat_rwlock_test4_name(krw_t type)
|
|
{
|
|
switch (type) {
|
|
case RW_NONE: return "RW_NONE";
|
|
case RW_WRITER: return "RW_WRITER";
|
|
case RW_READER: return "RW_READER";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test4_type(taskq_t *tq, rw_priv_t *rwp, int expected_rc,
|
|
krw_t holder_type, krw_t try_type)
|
|
{
|
|
int id, rc = 0;
|
|
|
|
/* Schedule a task function which will try and acquire the rwlock
|
|
* using type try_type while the rwlock is being held as holder_type.
|
|
* The result must match expected_rc for the test to pass */
|
|
rwp->rw_rc = -EINVAL;
|
|
rwp->rw_type = try_type;
|
|
|
|
if (holder_type == RW_WRITER || holder_type == RW_READER)
|
|
rw_enter(&rwp->rw_rwlock, holder_type);
|
|
|
|
id = taskq_dispatch(tq, splat_rwlock_test4_func, rwp, TQ_SLEEP);
|
|
if (id == 0) {
|
|
splat_vprint(rwp->rw_file, SPLAT_RWLOCK_TEST4_NAME, "%s",
|
|
"taskq_dispatch() failed\n");
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
taskq_wait_id(tq, id);
|
|
|
|
if (rwp->rw_rc != expected_rc)
|
|
rc = -EINVAL;
|
|
|
|
splat_vprint(rwp->rw_file, SPLAT_RWLOCK_TEST4_NAME,
|
|
"%srw_tryenter(%s) returned %d (expected %d) when %s\n",
|
|
rc ? "Fail " : "", splat_rwlock_test4_name(try_type),
|
|
rwp->rw_rc, expected_rc,
|
|
splat_rwlock_test4_name(holder_type));
|
|
out:
|
|
if (holder_type == RW_WRITER || holder_type == RW_READER)
|
|
rw_exit(&rwp->rw_rwlock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test4(struct file *file, void *arg)
|
|
{
|
|
rw_priv_t *rwp;
|
|
taskq_t *tq;
|
|
int rc = 0, rc1, rc2, rc3, rc4, rc5, rc6;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
tq = taskq_create(SPLAT_RWLOCK_TEST_TASKQ, 1, defclsyspri,
|
|
50, INT_MAX, TASKQ_PREPOPULATE);
|
|
if (tq == NULL) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
/* Validate all combinations of rw_tryenter() contention */
|
|
rc1 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_WRITER, RW_WRITER);
|
|
rc2 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_WRITER, RW_READER);
|
|
rc3 = splat_rwlock_test4_type(tq, rwp, -EBUSY, RW_READER, RW_WRITER);
|
|
rc4 = splat_rwlock_test4_type(tq, rwp, 0, RW_READER, RW_READER);
|
|
rc5 = splat_rwlock_test4_type(tq, rwp, 0, RW_NONE, RW_WRITER);
|
|
rc6 = splat_rwlock_test4_type(tq, rwp, 0, RW_NONE, RW_READER);
|
|
|
|
if (rc1 || rc2 || rc3 || rc4 || rc5 || rc6)
|
|
rc = -EINVAL;
|
|
|
|
taskq_destroy(tq);
|
|
out:
|
|
rw_destroy(&(rwp->rw_rwlock));
|
|
kfree(rwp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test5(struct file *file, void *arg)
|
|
{
|
|
rw_priv_t *rwp;
|
|
int rc = -EINVAL;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
rw_enter(&rwp->rw_rwlock, RW_WRITER);
|
|
if (!RW_WRITE_HELD(&rwp->rw_rwlock)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST5_NAME,
|
|
"rwlock should be write lock: %d\n",
|
|
RW_WRITE_HELD(&rwp->rw_rwlock));
|
|
goto out;
|
|
}
|
|
|
|
rw_downgrade(&rwp->rw_rwlock);
|
|
if (!RW_READ_HELD(&rwp->rw_rwlock)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST5_NAME,
|
|
"rwlock should be read lock: %d\n",
|
|
RW_READ_HELD(&rwp->rw_rwlock));
|
|
goto out;
|
|
}
|
|
|
|
rc = 0;
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST5_NAME, "%s",
|
|
"rwlock properly downgraded\n");
|
|
out:
|
|
rw_exit(&rwp->rw_rwlock);
|
|
rw_destroy(&rwp->rw_rwlock);
|
|
kfree(rwp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
splat_rwlock_test6(struct file *file, void *arg)
|
|
{
|
|
rw_priv_t *rwp;
|
|
int rc;
|
|
|
|
rwp = (rw_priv_t *)kmalloc(sizeof(*rwp), GFP_KERNEL);
|
|
if (rwp == NULL)
|
|
return -ENOMEM;
|
|
|
|
splat_init_rw_priv(rwp, file);
|
|
|
|
rw_enter(&rwp->rw_rwlock, RW_READER);
|
|
if (!RW_READ_HELD(&rwp->rw_rwlock)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST6_NAME,
|
|
"rwlock should be read lock: %d\n",
|
|
RW_READ_HELD(&rwp->rw_rwlock));
|
|
rc = -ENOLCK;
|
|
goto out;
|
|
}
|
|
|
|
/* With one reader upgrade should never fail. */
|
|
rc = rw_tryupgrade(&rwp->rw_rwlock);
|
|
if (!rc) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST6_NAME,
|
|
"rwlock failed upgrade from reader: %d\n",
|
|
RW_READ_HELD(&rwp->rw_rwlock));
|
|
rc = -ENOLCK;
|
|
goto out;
|
|
}
|
|
|
|
if (RW_READ_HELD(&rwp->rw_rwlock) || !RW_WRITE_HELD(&rwp->rw_rwlock)) {
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST6_NAME, "rwlock should "
|
|
"have 0 (not %d) reader and 1 (not %d) writer\n",
|
|
RW_READ_HELD(&rwp->rw_rwlock),
|
|
RW_WRITE_HELD(&rwp->rw_rwlock));
|
|
goto out;
|
|
}
|
|
|
|
rc = 0;
|
|
splat_vprint(file, SPLAT_RWLOCK_TEST6_NAME, "%s",
|
|
"rwlock properly upgraded\n");
|
|
out:
|
|
rw_exit(&rwp->rw_rwlock);
|
|
rw_destroy(&rwp->rw_rwlock);
|
|
kfree(rwp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
splat_subsystem_t *
|
|
splat_rwlock_init(void)
|
|
{
|
|
splat_subsystem_t *sub;
|
|
|
|
sub = kmalloc(sizeof(*sub), GFP_KERNEL);
|
|
if (sub == NULL)
|
|
return NULL;
|
|
|
|
memset(sub, 0, sizeof(*sub));
|
|
strncpy(sub->desc.name, SPLAT_RWLOCK_NAME, SPLAT_NAME_SIZE);
|
|
strncpy(sub->desc.desc, SPLAT_RWLOCK_DESC, SPLAT_DESC_SIZE);
|
|
INIT_LIST_HEAD(&sub->subsystem_list);
|
|
INIT_LIST_HEAD(&sub->test_list);
|
|
spin_lock_init(&sub->test_lock);
|
|
sub->desc.id = SPLAT_SUBSYSTEM_RWLOCK;
|
|
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST1_NAME, SPLAT_RWLOCK_TEST1_DESC,
|
|
SPLAT_RWLOCK_TEST1_ID, splat_rwlock_test1);
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST2_NAME, SPLAT_RWLOCK_TEST2_DESC,
|
|
SPLAT_RWLOCK_TEST2_ID, splat_rwlock_test2);
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST3_NAME, SPLAT_RWLOCK_TEST3_DESC,
|
|
SPLAT_RWLOCK_TEST3_ID, splat_rwlock_test3);
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST4_NAME, SPLAT_RWLOCK_TEST4_DESC,
|
|
SPLAT_RWLOCK_TEST4_ID, splat_rwlock_test4);
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST5_NAME, SPLAT_RWLOCK_TEST5_DESC,
|
|
SPLAT_RWLOCK_TEST5_ID, splat_rwlock_test5);
|
|
SPLAT_TEST_INIT(sub, SPLAT_RWLOCK_TEST6_NAME, SPLAT_RWLOCK_TEST6_DESC,
|
|
SPLAT_RWLOCK_TEST6_ID, splat_rwlock_test6);
|
|
|
|
return sub;
|
|
}
|
|
|
|
void
|
|
splat_rwlock_fini(splat_subsystem_t *sub)
|
|
{
|
|
ASSERT(sub);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST6_ID);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST5_ID);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST4_ID);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST3_ID);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST2_ID);
|
|
SPLAT_TEST_FINI(sub, SPLAT_RWLOCK_TEST1_ID);
|
|
kfree(sub);
|
|
}
|
|
|
|
int
|
|
splat_rwlock_id(void) {
|
|
return SPLAT_SUBSYSTEM_RWLOCK;
|
|
}
|