mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2024-11-18 02:20:59 +03:00
e811949a57
It turns out that the previous rwlock implementation worked well but did not integrate properly with the upstream kernel lock profiling/ analysis tools. This is a major problem since it would be awfully nice to be able to use the automatic lock checker and profiler. The problem is that the upstream lock tools use the pre-processor to create a lock class for each uniquely named locked. Since the rwsem was embedded in a wrapper structure the name was always the same. The effect was that we only ended up with one lock class for the entire SPL which caused the lock dependency checker to flag nearly everything as a possible deadlock. The solution was to directly map a krwlock to a Linux rwsem using a typedef there by eliminating the wrapper structure. This was not done initially because the rwsem implementation is specific to the arch. To fully implement the Solaris krwlock API using only the provided rwsem API is not possible. It can only be done by directly accessing some of the internal data member of the rwsem structure. For example, the Linux API provides a different function for dropping a reader vs writer lock. Whereas the Solaris API uses the same function and the caller does not pass in what type of lock it is. This means to properly drop the lock we need to determine if the lock is currently a reader or writer lock. Then we need to call the proper Linux API function. Unfortunately, there is no provided API for this so we must extracted this information directly from arch specific lock implementation. This is all do able, and what I did, but it does complicate things considerably. The good news is that in addition to the profiling benefits of this change. We may see performance improvements due to slightly reduced overhead when creating rwlocks and manipulating them. The only function I was forced to sacrafice was rw_owner() because this information is simply not stored anywhere in the rwsem. Luckily this appears not to be a commonly used function on Solaris, and it is my understanding it is mainly used for debugging anyway. In addition to the core rwlock changes, extensive updates were made to the rwlock regression tests. Each class of test was extended to provide more API coverage and to be more rigerous in checking for misbehavior. This is a pretty significant change and with that in mind I have been careful to validate it on several platforms before committing. The full SPLAT regression test suite was run numberous times on all of the following platforms. This includes various kernels ranging from 2.6.16 to 2.6.29. - SLES10 (ppc64) - SLES11 (x86_64) - CHAOS4.2 (x86_64) - RHEL5.3 (x86_64) - RHEL6 (x86_64) - FC11 (x86_64)
668 lines
19 KiB
C
668 lines
19 KiB
C
/*
|
|
* This file is part of the SPL: Solaris Porting Layer.
|
|
*
|
|
* Copyright (c) 2008 Lawrence Livermore National Security, LLC.
|
|
* Produced at Lawrence Livermore National Laboratory
|
|
* Written by:
|
|
* Brian Behlendorf <behlendorf1@llnl.gov>,
|
|
* Herb Wartens <wartens2@llnl.gov>,
|
|
* Jim Garlick <garlick@llnl.gov>
|
|
* UCRL-CODE-235197
|
|
*
|
|
* This 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.
|
|
*
|
|
* This 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 this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#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_type_t rw_type;
|
|
} rw_priv_t;
|
|
|
|
typedef struct rw_thr {
|
|
const char *rwt_name;
|
|
rw_priv_t *rwt_rwp;
|
|
int rwt_id;
|
|
} 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;
|
|
char name[16];
|
|
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
snprintf(name, sizeof(name), "rwlock_wr_thr%d", rwt->rwt_id);
|
|
daemonize(name);
|
|
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",
|
|
name, 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",
|
|
name, 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",
|
|
name, 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;
|
|
char name[16];
|
|
|
|
ASSERT(rwp->rw_magic == SPLAT_RWLOCK_TEST_MAGIC);
|
|
snprintf(name, sizeof(name), "rwlock_rd_thr%d", rwt->rwt_id);
|
|
daemonize(name);
|
|
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",
|
|
name, 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",
|
|
name, 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",
|
|
name, 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;
|
|
long pids[SPLAT_RWLOCK_TEST_COUNT];
|
|
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_id = i;
|
|
rwt[i].rwt_name = SPLAT_RWLOCK_TEST1_NAME;
|
|
|
|
/* The first thread will be the writer */
|
|
if (i == 0)
|
|
pids[i] = kernel_thread(splat_rwlock_wr_thr, &rwt[i], 0);
|
|
else
|
|
pids[i] = kernel_thread(splat_rwlock_rd_thr, &rwt[i], 0);
|
|
|
|
if (pids[i] >= 0)
|
|
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(),
|
|
maxclsyspri, 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, maxclsyspri,
|
|
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 = -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_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));
|
|
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 contended preventing upgrade: %d\n",
|
|
RW_COUNT(&rwp->rw_rwlock));
|
|
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;
|
|
}
|