diff options
43 files changed, 6322 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..b21cdf2fa --- /dev/null +++ b/ChangeLog @@ -0,0 +1,5 @@ +2008-02-26 Brian Behlendorf <[email protected]> + + : Initial commit of the solaris porting layer (spl). Included + in addition to the source is an initial autoconf / configure + style build system. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 000000000..7abb6ee39 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,9 @@ +AUTOMAKE_OPTIONS = foreign dist-zip + +SUBDIRS = src include scripts +CONFIG_CLEAN_FILES = aclocal.m4 config.guess config.sub +CONFIG_CLEAN_FILES += depcomp install-sh missing mkinstalldirs +EXTRA_DIST = autogen.sh + +rpms: dist Makefile + rpmbuild -ta $(distdir).tar.gz diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 000000000..340b2c673 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +find . -type d -name .deps | xargs rm -rf +aclocal 2>/dev/null && +autoheader && +automake --add-missing --include-deps # 2>/dev/null && +autoconf + diff --git a/configure.ac b/configure.ac new file mode 100644 index 000000000..52bc4f593 --- /dev/null +++ b/configure.ac @@ -0,0 +1,133 @@ +AC_INIT + +AC_CANONICAL_SYSTEM +AM_INIT_AUTOMAKE(spl, 0.0.1) +AC_CONFIG_HEADERS([config.h]) + +AC_PROG_INSTALL +AC_PROG_CC + +ver=`uname -r` +KERNELCFLAGS= + +kernelsrc= +kernelbuild= +AC_ARG_WITH(kernel, + [ --with-linux=PATH Path to kernel source ], + [kernelsrc="$withval"; kernelbuild="$withval"]) +AC_ARG_WITH(kernel-build, + [ --with-linux-obj=PATH Path to kernel build objects ], + [kernelbuild="$withval"]) + +AC_MSG_CHECKING([kernel source directory]) +if test -z "$kernelsrc"; then + kernelbuild= + sourcelink=/lib/modules/${ver}/source + buildlink=/lib/modules/${ver}/build + + if test -e $sourcelink; then + kernelsrc=`(cd $sourcelink; /bin/pwd)` + fi + if test -e $buildlink; then + kernelbuild=`(cd $buildlink; /bin/pwd)` + fi + if test -z "$kernelsrc"; then + kernelsrc=$kernelbuild + fi + if test -z "$kernelsrc" -o -z "$kernelbuild"; then + AC_MSG_RESULT([Not found]) + AC_MSG_ERROR([ + *** Please specify the location of the kernel source + *** with the '--with-kernel=PATH' option]) + fi +fi + +AC_MSG_RESULT([$kernelsrc]) +AC_MSG_CHECKING([kernel build directory]) +AC_MSG_RESULT([$kernelbuild]) + +AC_MSG_CHECKING([kernel source version]) +if test -r $kernelbuild/include/linux/version.h && + fgrep -q UTS_RELEASE $kernelbuild/include/linux/version.h; then + + kernsrcver=`(echo "#include <linux/version.h>"; + echo "kernsrcver=UTS_RELEASE") | + cpp -I $kernelbuild/include | + grep "^kernsrcver=" | cut -d \" -f 2` + +elif test -r $kernelbuild/include/linux/utsrelease.h && + fgrep -q UTS_RELEASE $kernelbuild/include/linux/utsrelease.h; then + + kernsrcver=`(echo "#include <linux/utsrelease.h>"; + echo "kernsrcver=UTS_RELEASE") | + cpp -I $kernelbuild/include | + grep "^kernsrcver=" | cut -d \" -f 2` +fi + +if test -z "$kernsrcver"; then + AC_MSG_RESULT([Not found]) + AC_MSG_ERROR([ + *** Cannot determine the version of the linux kernel source. + *** Please prepare the kernel before running this script]) +fi + +AC_MSG_RESULT([$kernsrcver]) +kmoduledir=${INSTALL_MOD_PATH}/lib/modules/$kernsrcver +AC_SUBST(kernelsrc) +AC_SUBST(kmoduledir) + +# +# Each version of the kernel provides a slightly different +# ABI, so figure out what we have to work with and adapt. +# +AC_MSG_CHECKING([if kernel defines kzalloc function]) +if egrep -qw "kzalloc" $kernelsrc/include/linux/slab.h; then + AC_DEFINE(HAVE_KZALLOC, 1, [kzalloc() is defined]) + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +AC_MSG_CHECKING([if inode has i_private field]) +if egrep -qw "i_private" $kernelsrc/include/linux/fs.h; then + AC_DEFINE(HAVE_I_PRIVATE, 1, [inode has i_private field]) + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +AC_MSG_CHECKING([if inode has i_mutex field ]) +if egrep -qw "i_mutex" $kernelsrc/include/linux/fs.h; then + AC_DEFINE(HAVE_I_MUTEX, 1, [inode has i_mutex field]) + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +AC_MSG_CHECKING([if kernel has mutex.h ]) +if test -f $kernelsrc/include/linux/mutex.h; then + AC_DEFINE(HAVE_MUTEX_H, 1, [kernel has mutex.h]) + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +if test "$kernelbuild" != "$kernelsrc"; then + KERNELMAKE_PARAMS="$KERNELMAKE_PARAMS O=$kernelbuild" +fi + +AC_SUBST(KERNELMAKE_PARAMS) +AC_SUBST(KERNELCPPFLAGS) +AC_SUBST(KERNELCFLAGS) + +AC_CONFIG_FILES([ Makefile + src/Makefile + src/cmd/Makefile + src/spl/Makefile + src/splat/Makefile + include/Makefile + scripts/Makefile + scripts/spl.spec + ]) + +AC_OUTPUT diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 000000000..42bbb2399 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,5 @@ +EXTRA_DIST = spl.h splat.h splat-ctl.h +EXTRA_DIST += linux-condvar.h linux-kmem.h linux-random.h linux-thread.h +EXTRA_DIST += linux-types.h linux-cred.h linux-kstat.h linux-rwlock.h +EXTRA_DIST += linux-time.h linux-callb.h linux-generic.h linux-mutex.h +EXTRA_DIST += linux-taskq.h linux-timer.h diff --git a/include/linux-callb.h b/include/linux-callb.h new file mode 100644 index 000000000..5ddb678b3 --- /dev/null +++ b/include/linux-callb.h @@ -0,0 +1,45 @@ +#ifndef _SYS_LINUX_CALLB_H +#define _SYS_LINUX_CALLB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/linux-mutex.h> + +#define DEBUG_CALLB + +#ifndef DEBUG_CALLB +#define CALLB_CPR_ASSERT(cp) BUG_ON(!(MUTEX_HELD((cp)->cc_lockp))); +#else +#define CALLB_CPR_ASSERT(cp) +#endif + + +typedef struct callb_cpr { + kmutex_t *cc_lockp; +} callb_cpr_t; + +#define CALLB_CPR_INIT(cp, lockp, func, name) { \ + (cp)->cc_lockp = lockp; \ +} + +#define CALLB_CPR_SAFE_BEGIN(cp) { \ + CALLB_CPR_ASSERT(cp); \ +} + +#define CALLB_CPR_SAFE_END(cp, lockp) { \ + CALLB_CPR_ASSERT(cp); \ +} + +#define CALLB_CPR_EXIT(cp) { \ + ASSERT(MUTEX_HELD((cp)->cc_lockp)); \ + mutex_exit((cp)->cc_lockp); \ +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_CALLB_H */ + diff --git a/include/linux-condvar.h b/include/linux-condvar.h new file mode 100644 index 000000000..33ad51539 --- /dev/null +++ b/include/linux-condvar.h @@ -0,0 +1,201 @@ +#ifndef _SYS_LINUX_CONDVAR_H +#define _SYS_LINUX_CONDVAR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/wait.h> + +/* The kcondvar_t struct is protected by mutex taken externally before + * calling any of the wait/signal funs, and passed into the wait funs. + */ +#define CV_MAGIC 0x346545f4 +#define CV_POISON 0x95 + +typedef struct { + int cv_magic; + char *cv_name; + wait_queue_head_t cv_event; + atomic_t cv_waiters; + kmutex_t *cv_mutex; /* only for verification purposes */ +} kcondvar_t; + +typedef enum { CV_DEFAULT=0, CV_DRIVER } kcv_type_t; + +static __inline__ void +cv_init(kcondvar_t *cvp, char *name, kcv_type_t type, void *arg) +{ + BUG_ON(cvp == NULL); + BUG_ON(type != CV_DEFAULT); + BUG_ON(arg != NULL); + + cvp->cv_magic = CV_MAGIC; + init_waitqueue_head(&cvp->cv_event); + atomic_set(&cvp->cv_waiters, 0); + cvp->cv_mutex = NULL; + cvp->cv_name = NULL; + + if (name) { + cvp->cv_name = kmalloc(strlen(name) + 1, GFP_KERNEL); + if (cvp->cv_name) + strcpy(cvp->cv_name, name); + } +} + +static __inline__ void +cv_destroy(kcondvar_t *cvp) +{ + BUG_ON(cvp == NULL); + BUG_ON(cvp->cv_magic != CV_MAGIC); + BUG_ON(atomic_read(&cvp->cv_waiters) != 0); + BUG_ON(waitqueue_active(&cvp->cv_event)); + + if (cvp->cv_name) + kfree(cvp->cv_name); + + memset(cvp, CV_POISON, sizeof(*cvp)); +} + +static __inline__ void +cv_wait(kcondvar_t *cvp, kmutex_t *mtx) +{ + DEFINE_WAIT(wait); + int flag = 1; + + BUG_ON(cvp == NULL || mtx == NULL); + BUG_ON(cvp->cv_magic != CV_MAGIC); + BUG_ON(!mutex_owned(mtx)); + + if (cvp->cv_mutex == NULL) + cvp->cv_mutex = mtx; + + /* Ensure the same mutex is used by all callers */ + BUG_ON(cvp->cv_mutex != mtx); + + for (;;) { + prepare_to_wait_exclusive(&cvp->cv_event, &wait, + TASK_INTERRUPTIBLE); + /* Must occur after we are added to the list but only once */ + if (flag) { + atomic_inc(&cvp->cv_waiters); + flag = 0; + } + + /* XXX - The correct thing to do here may be to wake up and + * force the caller to handle the signal. Spurious wakeups + * should already be safely handled by the caller. */ + if (signal_pending(current)) + flush_signals(current); + + /* Mutex should be dropped after prepare_to_wait() this + * ensures we're linked in to the waiters list and avoids the + * race where 'cvp->cv_waiters > 0' but the list is empty. */ + mutex_exit(mtx); + schedule(); + mutex_enter(mtx); + + /* XXX - The correct thing to do here may be to wake up and + * force the caller to handle the signal. Spurious wakeups + * should already be safely handled by the caller. */ + if (signal_pending(current)) + continue; + + break; + } + + atomic_dec(&cvp->cv_waiters); + finish_wait(&cvp->cv_event, &wait); +} + +/* 'expire_time' argument is an absolute wall clock time in jiffies. + * Return value is time left (expire_time - now) or -1 if timeout occurred. + */ +static __inline__ clock_t +cv_timedwait(kcondvar_t *cvp, kmutex_t *mtx, clock_t expire_time) +{ + DEFINE_WAIT(wait); + clock_t time_left; + int flag = 1; + + BUG_ON(cvp == NULL || mtx == NULL); + BUG_ON(cvp->cv_magic != CV_MAGIC); + BUG_ON(!mutex_owned(mtx)); + + if (cvp->cv_mutex == NULL) + cvp->cv_mutex = mtx; + + /* XXX - Does not handle jiffie wrap properly */ + time_left = expire_time - jiffies; + if (time_left <= 0) + return -1; + + /* Ensure the same mutex is used by all callers */ + BUG_ON(cvp->cv_mutex != mtx); + + for (;;) { + prepare_to_wait_exclusive(&cvp->cv_event, &wait, + TASK_INTERRUPTIBLE); + if (flag) { + atomic_inc(&cvp->cv_waiters); + flag = 0; + } + + /* XXX - The correct thing to do here may be to wake up and + * force the caller to handle the signal. Spurious wakeups + * should already be safely handled by the caller. */ + if (signal_pending(current)) + flush_signals(current); + + /* Mutex should be dropped after prepare_to_wait() this + * ensures we're linked in to the waiters list and avoids the + * race where 'cvp->cv_waiters > 0' but the list is empty. */ + mutex_exit(mtx); + time_left = schedule_timeout(time_left); + mutex_enter(mtx); + + /* XXX - The correct thing to do here may be to wake up and + * force the caller to handle the signal. Spurious wakeups + * should already be safely handled by the caller. */ + if (signal_pending(current)) { + if (time_left > 0) + continue; + + flush_signals(current); + } + + break; + } + + atomic_dec(&cvp->cv_waiters); + finish_wait(&cvp->cv_event, &wait); + + return (time_left > 0 ? time_left : -1); +} + +static __inline__ void +cv_signal(kcondvar_t *cvp) +{ + BUG_ON(cvp == NULL); + BUG_ON(cvp->cv_magic != CV_MAGIC); + + /* All waiters are added with WQ_FLAG_EXCLUSIVE so only one + * waiter will be set runable with each call to wake_up(). + * Additionally wake_up() holds a spin_lock assoicated with + * the wait queue to ensure we don't race waking up processes. */ + if (atomic_read(&cvp->cv_waiters) > 0) + wake_up(&cvp->cv_event); +} + +static __inline__ void +cv_broadcast(kcondvar_t *cvp) +{ + BUG_ON(cvp == NULL); + BUG_ON(cvp->cv_magic != CV_MAGIC); + + /* Wake_up_all() will wake up all waiters even those which + * have the WQ_FLAG_EXCLUSIVE flag set. */ + if (atomic_read(&cvp->cv_waiters) > 0) + wake_up_all(&cvp->cv_event); +} +#endif /* _SYS_LINUX_CONDVAR_H */ diff --git a/include/linux-cred.h b/include/linux-cred.h new file mode 100644 index 000000000..5f308bace --- /dev/null +++ b/include/linux-cred.h @@ -0,0 +1,40 @@ +#ifndef _SYS_LINUX_CRED_H +#define _SYS_LINUX_CRED_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/types.h> + +/* XXX - Portions commented out because we really just want to have the type + * defined and the contents aren't nearly so important at the moment. */ +typedef struct cred { + uint_t cr_ref; /* reference count */ + uid_t cr_uid; /* effective user id */ + gid_t cr_gid; /* effective group id */ + uid_t cr_ruid; /* real user id */ + gid_t cr_rgid; /* real group id */ + uid_t cr_suid; /* "saved" user id (from exec) */ + gid_t cr_sgid; /* "saved" group id (from exec) */ + uint_t cr_ngroups; /* number of groups returned by */ + /* crgroups() */ +#if 0 + cred_priv_t cr_priv; /* privileges */ + projid_t cr_projid; /* project */ + struct zone *cr_zone; /* pointer to per-zone structure */ + struct ts_label_s *cr_label; /* pointer to the effective label */ + credsid_t *cr_ksid; /* pointer to SIDs */ +#endif + gid_t cr_groups[1]; /* cr_groups size not fixed */ + /* audit info is defined dynamically */ + /* and valid only when audit enabled */ + /* auditinfo_addr_t cr_auinfo; audit info */ +} cred_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_CRED_H */ + diff --git a/include/linux-generic.h b/include/linux-generic.h new file mode 100644 index 000000000..5c4f9146a --- /dev/null +++ b/include/linux-generic.h @@ -0,0 +1,72 @@ +#ifndef _SYS_LINUX_GENERIC_H +#define _SYS_LINUX_GENERIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Missing defines. + */ +#define INT32_MAX INT_MAX +#define UINT64_MAX (~0ULL) +#define NBBY 8 +#define ENOTSUP ENOTSUPP +#define MAXNAMELEN 256 +#define MAXPATHLEN PATH_MAX +#define __va_list va_list +#define _KERNEL 1 +#define max_ncpus 64 + +/* 0..MAX_PRIO-1: Process priority + * 0..MAX_RT_PRIO-1: RT priority tasks + * MAX_RT_PRIO..MAX_PRIO-1: SCHED_NORMAL tasks + * + * Treat shim tasks as SCHED_NORMAL tasks + */ +#define minclsyspri (MAX_RT_PRIO) +#define maxclsyspri (MAX_PRIO-1) + +#define NICE_TO_PRIO(nice) (MAX_RT_PRIO + (nice) + 20) +#define PRIO_TO_NICE(prio) ((prio) - MAX_RT_PRIO - 20) + +#define kred NULL + +#define FREAD 1 +#define FWRITE 2 +#define FCREAT O_CREAT +#define FTRUNC O_TRUNC +#define FOFFMAX O_LARGEFILE +#define FSYNC O_SYNC +#define FDSYNC O_DSYNC +#define FRSYNC O_RSYNC +#define FEXCL O_EXCL + +#define FNODSYNC 0x10000 /* fsync pseudo flag */ +#define FNOFOLLOW 0x20000 /* don't follow symlinks */ + +/* Missing macros + */ +#define PAGESIZE PAGE_SIZE + +/* from Solaris sys/byteorder.h */ +#define BSWAP_8(x) ((x) & 0xff) +#define BSWAP_16(x) ((BSWAP_8(x) << 8) | BSWAP_8((x) >> 8)) +#define BSWAP_32(x) ((BSWAP_16(x) << 16) | BSWAP_16((x) >> 16)) +#define BSWAP_64(x) ((BSWAP_32(x) << 32) | BSWAP_32((x) >> 32)) + +/* Map some simple functions. + */ +#define bzero(ptr,size) memset(ptr,0,size) +#define bcopy(src,dest,size) memcpy(dest,src,size) +#define ASSERT(x) BUG_ON(!(x)) +#define ASSERT3U(left,OP,right) BUG_ON(!((left) OP (right))) + +/* Missing globals + */ +static int p0 = 0; + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_GENERIC_H */ diff --git a/include/linux-kmem.h b/include/linux-kmem.h new file mode 100644 index 000000000..c68344cf3 --- /dev/null +++ b/include/linux-kmem.h @@ -0,0 +1,173 @@ +#ifndef _SYS_LINUX_KMEM_H +#define _SYS_LINUX_KMEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#undef DEBUG_KMEM +#undef DEBUG_KMEM_UNIMPLEMENTED + +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/spinlock.h> +/* + * Memory allocation interfaces + */ +#define KM_SLEEP GFP_KERNEL +#define KM_NOSLEEP GFP_ATOMIC +#undef KM_PANIC /* No linux analog */ +#define KM_PUSHPAGE (GFP_KERNEL | GFP_HIGH) +#define KM_VMFLAGS GFP_LEVEL_MASK +#define KM_FLAGS __GFP_BITS_MASK + +#ifdef DEBUG_KMEM +/* Shim layer memory accounting */ +extern atomic_t kmem_alloc_used; +extern unsigned int kmem_alloc_max; +#endif + +#ifdef DEBUG_KMEM +#define __kmem_alloc(size, flags, allocator) \ +({ void *_ptr_; \ + \ + /* Marked unlikely because we should never be doing this */ \ + if (unlikely((size) > (PAGE_SIZE * 2))) \ + printk("Warning: kmem_alloc(%d, 0x%x) large alloc at %s:%d " \ + "(%d/%d)\n", (int)(size), (int)(flags), \ + __FILE__, __LINE__, \ + atomic_read(&kmem_alloc_used), kmem_alloc_max); \ + \ + _ptr_ = (void *)allocator((size), (flags)); \ + if (_ptr_ == NULL) { \ + printk("Warning: kmem_alloc(%d, 0x%x) failed at %s:%d " \ + "(%d/%d)\n", (int)(size), (int)(flags), \ + __FILE__, __LINE__, \ + atomic_read(&kmem_alloc_used), kmem_alloc_max); \ + atomic_add((size), &kmem_alloc_used); \ + if (unlikely(atomic_read(&kmem_alloc_used) > kmem_alloc_max)) \ + kmem_alloc_max = atomic_read(&kmem_alloc_used); \ + } \ + \ + _ptr_; \ +}) + +#define kmem_alloc(size, flags) __kmem_alloc(size, flags, kmalloc) +#define kmem_zalloc(size, flags) __kmem_alloc(size, flags, kzalloc) + +#define kmem_free(ptr, size) \ +({ \ + BUG_ON(!ptr); \ + atomic_sub((size), &kmem_alloc_used); \ + memset(ptr, 0x5a, (size)); /* Poison */ \ + kfree(ptr); \ + (ptr) = (void *)0xdeadbeef; \ +}) + + +#else + +#define kmem_alloc(size, flags) kmalloc(size, flags) +#define kmem_zalloc(size, flags) kzalloc(size, flags) +#define kmem_free(ptr, size) kfree(ptr) + +#endif /* DEBUG_KMEM */ + + +#ifdef DEBUG_KMEM_UNIMPLEMENTED +static __inline__ void * +kmem_alloc_tryhard(size_t size, size_t *alloc_size, int kmflags) +{ +#error "kmem_alloc_tryhard() not implemented" +} +#endif /* DEBUG_KMEM_UNIMPLEMENTED */ + +/* + * Slab allocation interfaces + */ +#undef KMC_NOTOUCH /* No linux analog */ +#define KMC_NODEBUG 0x00000000 /* Default beahvior */ +#define KMC_NOMAGAZINE /* No linux analog */ +#define KMC_NOHASH /* No linux analog */ +#define KMC_QCACHE /* No linux analog */ + +#define KMC_REAP_CHUNK 256 +#define KMC_DEFAULT_SEEKS DEFAULT_SEEKS + +/* Defined by linux slab.h + * typedef struct kmem_cache_s kmem_cache_t; + */ + +/* No linux analog + * extern int kmem_ready; + * extern pgcnt_t kmem_reapahead; + */ + +#ifdef DEBUG_KMEM_UNIMPLEMENTED +static __inline__ void kmem_init(void) { +#error "kmem_init() not implemented" +} + +static __inline__ void kmem_thread_init(void) { +#error "kmem_thread_init() not implemented" +} + +static __inline__ void kmem_mp_init(void) { +#error "kmem_mp_init() not implemented" +} + +static __inline__ void kmem_reap_idspace(void) { +#error "kmem_reap_idspace() not implemented" +} + +static __inline__ size_t kmem_avail(void) { +#error "kmem_avail() not implemented" +} + +static __inline__ size_t kmem_maxavail(void) { +#error "kmem_maxavail() not implemented" +} + +static __inline__ uint64_t kmem_cache_stat(kmem_cache_t *cache) { +#error "kmem_cache_stat() not implemented" +} +#endif /* DEBUG_KMEM_UNIMPLEMENTED */ + +/* XXX - Used by arc.c to adjust its memory footprint. We may want + * to use this hook in the future to adjust behavior based on + * debug levels. For now it's safe to always return 0. + */ +static __inline__ int +kmem_debugging(void) +{ + return 0; +} + +typedef int (*kmem_constructor_t)(void *, void *, int); +typedef void (*kmem_destructor_t)(void *, void *); +typedef void (*kmem_reclaim_t)(void *); + +kmem_cache_t * +__kmem_cache_create(char *name, size_t size, size_t align, + int (*constructor)(void *, void *, int), + void (*destructor)(void *, void *), + void (*reclaim)(void *), + void *priv, void *vmp, int flags); + +void +__kmem_cache_destroy(kmem_cache_t *cache); + +#define kmem_cache_create(name,size,align,ctor,dtor,rclm,priv,vmp,flags) \ + __kmem_cache_create(name,size,align,ctor,dtor,rclm,priv,vmp,flags) +#define kmem_cache_destroy(cache) __kmem_cache_destroy(cache) +#define kmem_cache_alloc(cache, flags) kmem_cache_alloc(cache, flags) +#define kmem_cache_free(cache, ptr) kmem_cache_free(cache, ptr) +#define kmem_cache_reap_now(cache) kmem_cache_shrink(cache) +#define kmem_reap() __kmem_reap() + + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_KMEM_H */ diff --git a/include/linux-kstat.h b/include/linux-kstat.h new file mode 100644 index 000000000..738dbc867 --- /dev/null +++ b/include/linux-kstat.h @@ -0,0 +1,136 @@ +#ifndef _SYS_LINUX_KSTAT_H +#define _SYS_LINUX_KSTAT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/linux-types.h> + +/* XXX - The minimum functionality here is stubbed out but nothing works. */ + +#define KSTAT_STRLEN 31 /* 30 chars + NULL; must be 16 * n - 1 */ + +#define KSTAT_TYPE_RAW 0 /* can be anything */ + /* ks_ndata >= 1 */ +#define KSTAT_TYPE_NAMED 1 /* name/value pair */ + /* ks_ndata >= 1 */ +#define KSTAT_TYPE_INTR 2 /* interrupt statistics */ + /* ks_ndata == 1 */ +#define KSTAT_TYPE_IO 3 /* I/O statistics */ + /* ks_ndata == 1 */ +#define KSTAT_TYPE_TIMER 4 /* event timer */ + /* ks_ndata >= 1 */ + +#define KSTAT_NUM_TYPES 5 + + +#define KSTAT_DATA_CHAR 0 +#define KSTAT_DATA_INT32 1 +#define KSTAT_DATA_UINT32 2 +#define KSTAT_DATA_INT64 3 +#define KSTAT_DATA_UINT64 4 + + +#define KSTAT_FLAG_VIRTUAL 0x01 +#define KSTAT_FLAG_VAR_SIZE 0x02 +#define KSTAT_FLAG_WRITABLE 0x04 +#define KSTAT_FLAG_PERSISTENT 0x08 +#define KSTAT_FLAG_DORMANT 0x10 +#define KSTAT_FLAG_INVALID 0x2 + + +typedef int kid_t; /* unique kstat id */ + +typedef struct kstat_s { + /* + * Fields relevant to both kernel and user + */ + hrtime_t ks_crtime; /* creation time (from gethrtime()) */ + struct kstat_s *ks_next; /* kstat chain linkage */ + kid_t ks_kid; /* unique kstat ID */ + char ks_module[KSTAT_STRLEN]; /* provider module name */ + uchar_t ks_resv; /* reserved, currently just padding */ + int ks_instance; /* provider module's instance */ + char ks_name[KSTAT_STRLEN]; /* kstat name */ + uchar_t ks_type; /* kstat data type */ + char ks_class[KSTAT_STRLEN]; /* kstat class */ + uchar_t ks_flags; /* kstat flags */ + void *ks_data; /* kstat type-specific data */ + uint_t ks_ndata; /* # of type-specific data records */ + size_t ks_data_size; /* total size of kstat data section */ + hrtime_t ks_snaptime; /* time of last data shapshot */ + /* + * Fields relevant to kernel only + */ + int (*ks_update)(struct kstat *, int); /* dynamic update */ + void *ks_private; /* arbitrary provider-private data */ + int (*ks_snapshot)(struct kstat *, void *, int); + void *ks_lock; /* protects this kstat's data */ +} kstat_t; + +typedef struct kstat_named_s { + char name[KSTAT_STRLEN]; /* name of counter */ + uchar_t data_type; /* data type */ + union { + char c[16]; /* enough for 128-bit ints */ + int32_t i32; + uint32_t ui32; + struct { + union { + char *ptr; /* NULL-term string */ + char __pad[8]; /* 64-bit padding */ + } addr; + uint32_t len; /* # bytes for strlen + '\0' */ + } str; +/* + * The int64_t and uint64_t types are not valid for a maximally conformant + * 32-bit compilation environment (cc -Xc) using compilers prior to the + * introduction of C99 conforming compiler (reference ISO/IEC 9899:1990). + * In these cases, the visibility of i64 and ui64 is only permitted for + * 64-bit compilation environments or 32-bit non-maximally conformant + * C89 or C90 ANSI C compilation environments (cc -Xt and cc -Xa). In the + * C99 ANSI C compilation environment, the long long type is supported. + * The _INT64_TYPE is defined by the implementation (see sys/int_types.h). + */ + int64_t i64; + uint64_t ui64; + long l; + ulong_t ul; + + /* These structure members are obsolete */ + + longlong_t ll; + u_longlong_t ull; + float f; + double d; + } value; /* value of counter */ +} kstat_named_t; + + +static __inline__ kstat_t * +kstat_create(const char *ks_module, int ks_instance, const char *ks_name, + const char *ks_class, uchar_t ks_type, uint_t ks_ndata, + uchar_t ks_flags) +{ + return NULL; +} + +static __inline__ void +kstat_install(kstat_t *ksp) +{ + return; +} + +static __inline__ void +kstat_delete(kstat_t *ksp) +{ + return; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_KSTAT_H */ + diff --git a/include/linux-mutex.h b/include/linux-mutex.h new file mode 100644 index 000000000..42056617f --- /dev/null +++ b/include/linux-mutex.h @@ -0,0 +1,118 @@ +#ifndef _SYS_LINUX_MUTEX_H +#define _SYS_LINUX_MUTEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* See the "Big Theory Statement" in solaris mutex.c. + * + * Spin mutexes apparently aren't needed by zfs so we assert + * if ibc is non-zero. + * + * Our impementation of adaptive mutexes aren't really adaptive. + * They go to sleep every time. + */ + +#define MUTEX_DEFAULT 0 +#define MUTEX_HELD(x) (mutex_owned(x)) + +#define KM_MAGIC 0x42424242 +#define KM_POISON 0x84 + +typedef struct { + int km_magic; + char *km_name; + struct task_struct *km_owner; + struct semaphore km_sem; +} kmutex_t; + +#undef mutex_init +static __inline__ void +mutex_init(kmutex_t *mp, char *name, int type, void *ibc) +{ + BUG_ON(ibc != NULL); /* XXX - Spin mutexes not needed? */ + BUG_ON(type != MUTEX_DEFAULT); /* XXX - Only default type supported? */ + + mp->km_magic = KM_MAGIC; + sema_init(&mp->km_sem, 1); + mp->km_owner = NULL; + mp->km_name = NULL; + + if (name) { + mp->km_name = kmalloc(strlen(name) + 1, GFP_KERNEL); + if (mp->km_name) + strcpy(mp->km_name, name); + } +} + +#undef mutex_destroy +static __inline__ void +mutex_destroy(kmutex_t *mp) +{ + BUG_ON(mp->km_magic != KM_MAGIC); + + if (mp->km_name) + kfree(mp->km_name); + + memset(mp, KM_POISON, sizeof(*mp)); +} + +static __inline__ void +mutex_enter(kmutex_t *mp) +{ + BUG_ON(mp->km_magic != KM_MAGIC); + down(&mp->km_sem); /* Will check in_atomic() for us */ + BUG_ON(mp->km_owner != NULL); + mp->km_owner = current; +} + +/* Return 1 if we acquired the mutex, else zero. + */ +static __inline__ int +mutex_tryenter(kmutex_t *mp) +{ + int result; + + BUG_ON(mp->km_magic != KM_MAGIC); + result = down_trylock(&mp->km_sem); /* returns 0 if acquired */ + if (result == 0) { + BUG_ON(mp->km_owner != NULL); + mp->km_owner = current; + return 1; + } + return 0; +} + +static __inline__ void +mutex_exit(kmutex_t *mp) +{ + BUG_ON(mp->km_magic != KM_MAGIC); + BUG_ON(mp->km_owner != current); + mp->km_owner = NULL; + up(&mp->km_sem); +} + +/* Return 1 if mutex is held by current process, else zero. + */ +static __inline__ int +mutex_owned(kmutex_t *mp) +{ + BUG_ON(mp->km_magic != KM_MAGIC); + return (mp->km_owner == current); +} + +/* Return owner if mutex is owned, else NULL. + */ +static __inline__ kthread_t * +mutex_owner(kmutex_t *mp) +{ + BUG_ON(mp->km_magic != KM_MAGIC); + return mp->km_owner; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_MUTEX_H */ diff --git a/include/linux-random.h b/include/linux-random.h new file mode 100644 index 000000000..51233d48f --- /dev/null +++ b/include/linux-random.h @@ -0,0 +1,37 @@ +#ifndef _SYS_LINUX_RANDOM_H +#define _SYS_LINUX_RANDOM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/random.h> + +/* FIXME: + * Should add support for blocking in the future to + * ensure that proper entopy is collected. ZFS doesn't + * use it at the moment so this is good enough for now. + * Always will succeed by returning 0. + */ +static __inline__ int +random_get_bytes(uint8_t *ptr, size_t len) +{ + BUG_ON(len < 0); + get_random_bytes((void *)ptr,(int)len); + return 0; +} + + /* Always will succeed by returning 0. */ +static __inline__ int +random_get_pseudo_bytes(uint8_t *ptr, size_t len) +{ + BUG_ON(len < 0); + get_random_bytes((void *)ptr,(int)len); + return 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_RANDOM_H */ diff --git a/include/linux-rwlock.h b/include/linux-rwlock.h new file mode 100644 index 000000000..a6a2787d8 --- /dev/null +++ b/include/linux-rwlock.h @@ -0,0 +1,223 @@ +#ifndef _SYS_LINUX_RWLOCK_H +#define _SYS_LINUX_RWLOCK_H + +#include <linux/slab.h> +#include <linux/rwsem.h> +#include <asm/current.h> +#include <sys/linux-types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + RW_DRIVER = 2, /* driver (DDI) rwlock */ + RW_DEFAULT = 4 /* kernel default rwlock */ +} krw_type_t; + +typedef enum { + RW_WRITER, + RW_READER +} krw_t; + +#define RW_READ_HELD(x) (rw_read_held((x))) +#define RW_WRITE_HELD(x) (rw_write_held((x))) +#define RW_LOCK_HELD(x) (rw_lock_held((x))) +#define RW_ISWRITER(x) (rw_iswriter(x)) + +#define RW_MAGIC 0x3423645a +#define RW_POISON 0xa6 + +typedef struct { + int rw_magic; + char *rw_name; + struct rw_semaphore rw_sem; + struct task_struct *rw_owner; /* holder of the write lock */ +} krwlock_t; + +static __inline__ void +rw_init(krwlock_t *rwlp, char *name, krw_type_t type, void *arg) +{ + BUG_ON(type != RW_DEFAULT); /* XXX no irq handler use */ + BUG_ON(arg != NULL); /* XXX no irq handler use */ + rwlp->rw_magic = RW_MAGIC; + rwlp->rw_owner = NULL; /* no one holds the write lock yet */ + init_rwsem(&rwlp->rw_sem); + rwlp->rw_name = NULL; + + if (name) { + rwlp->rw_name = kmalloc(strlen(name) + 1, GFP_KERNEL); + if (rwlp->rw_name) + strcpy(rwlp->rw_name, name); + } +} + +static __inline__ void +rw_destroy(krwlock_t *rwlp) +{ + BUG_ON(rwlp == NULL); + BUG_ON(rwlp->rw_magic != RW_MAGIC); + BUG_ON(rwlp->rw_owner != NULL); + spin_lock(&rwlp->rw_sem.wait_lock); + BUG_ON(!list_empty(&rwlp->rw_sem.wait_list)); + spin_unlock(&rwlp->rw_sem.wait_lock); + + if (rwlp->rw_name) + kfree(rwlp->rw_name); + + memset(rwlp, RW_POISON, sizeof(krwlock_t)); +} + +/* Return 0 if the lock could not be obtained without blocking. + */ +static __inline__ int +rw_tryenter(krwlock_t *rwlp, krw_t rw) +{ + int result; + + BUG_ON(rwlp->rw_magic != RW_MAGIC); + switch (rw) { + /* these functions return 1 if success, 0 if contention */ + case RW_READER: + /* Here the Solaris code would return 0 + * if there were any write waiters. Specifically + * thinking about the case where readers may have + * the lock and we would also allow this thread + * to grab the read lock with a writer waiting in the + * queue. This doesn't seem like a correctness + * issue, so just call down_read_trylock() + * for the test. We may have to revisit this if + * it becomes an issue */ + result = down_read_trylock(&rwlp->rw_sem); + break; + case RW_WRITER: + result = down_write_trylock(&rwlp->rw_sem); + if (result) { + /* there better not be anyone else + * holding the write lock here */ + BUG_ON(rwlp->rw_owner != NULL); + rwlp->rw_owner = current; + } + break; + } + + return result; +} + +static __inline__ void +rw_enter(krwlock_t *rwlp, krw_t rw) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + switch (rw) { + case RW_READER: + /* Here the Solaris code would block + * if there were any write waiters. Specifically + * thinking about the case where readers may have + * the lock and we would also allow this thread + * to grab the read lock with a writer waiting in the + * queue. This doesn't seem like a correctness + * issue, so just call down_read() + * for the test. We may have to revisit this if + * it becomes an issue */ + down_read(&rwlp->rw_sem); + break; + case RW_WRITER: + down_write(&rwlp->rw_sem); + + /* there better not be anyone else + * holding the write lock here */ + BUG_ON(rwlp->rw_owner != NULL); + rwlp->rw_owner = current; + break; + } +} + +static __inline__ void +rw_exit(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + + /* rw_owner is held by current + * thread iff it is a writer */ + if (rwlp->rw_owner == current) { + rwlp->rw_owner = NULL; + up_write(&rwlp->rw_sem); + } else { + up_read(&rwlp->rw_sem); + } +} + +static __inline__ void +rw_downgrade(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + BUG_ON(rwlp->rw_owner != current); + rwlp->rw_owner = NULL; + downgrade_write(&rwlp->rw_sem); +} + +/* Return 0 if unable to perform the upgrade. + * Might be wise to fix the caller + * to acquire the write lock first? + */ +static __inline__ int +rw_tryupgrade(krwlock_t *rwlp) +{ + int result; + BUG_ON(rwlp->rw_magic != RW_MAGIC); + + spin_lock(&rwlp->rw_sem.wait_lock); + + /* Check if there is anyone waiting for the + * lock. If there is, then we know we should + * not try to upgrade the lock */ + if (!list_empty(&rwlp->rw_sem.wait_list)) { + printk(KERN_WARNING "There are threads waiting\n"); + spin_unlock(&rwlp->rw_sem.wait_lock); + return 0; + } +#ifdef CONFIG_RWSEM_GENERIC_SPINLOCK + /* Note that activity is protected by + * the wait_lock. Don't try to upgrade + * if there are multiple readers currently + * holding the lock */ + if (rwlp->rw_sem.activity > 1) { +#else + /* Don't try to upgrade + * if there are multiple readers currently + * holding the lock */ + if ((rwlp->rw_sem.count & RWSEM_ACTIVE_MASK) > 1) { +#endif + spin_unlock(&rwlp->rw_sem.wait_lock); + return 0; + } + + /* Here it should be safe to drop the + * read lock and reacquire it for writing since + * we know there are no waiters */ + up_read(&rwlp->rw_sem); + + /* returns 1 if success, 0 if contention */ + result = down_write_trylock(&rwlp->rw_sem); + + /* Check if upgrade failed. Should not ever happen + * if we got to this point */ + BUG_ON(!result); + BUG_ON(rwlp->rw_owner != NULL); + rwlp->rw_owner = current; + spin_unlock(&rwlp->rw_sem.wait_lock); + return 1; +} + +static __inline__ kthread_t * +rw_owner(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + return rwlp->rw_owner; +} + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_RWLOCK_H */ diff --git a/include/linux-taskq.h b/include/linux-taskq.h new file mode 100644 index 000000000..3612f84c0 --- /dev/null +++ b/include/linux-taskq.h @@ -0,0 +1,86 @@ +#ifndef _SYS_LINUX_TASKQ_H +#define _SYS_LINUX_TASKQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Task Queues - As of linux 2.6.x task queues have been replaced by a + * similar construct called work queues. The big difference on the linux + * side is that functions called from work queues run in process context + * and not interrupt context. + * + * One nice feature of Solaris which does not exist in linux work + * queues in the notion of a dynamic work queue. Rather than implementing + * this in the shim layer I'm hardcoding one-thread per work queue. + * + * XXX - This may end up being a significant performance penalty which + * forces us to implement dynamic workqueues. Which is all very doable + * with a little effort. + */ +#include <linux/workqueue.h> +#include <linux/gfp.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <sys/linux-types.h> + +#undef DEBUG_TASKQ_UNIMPLEMENTED + +#define TASKQ_NAMELEN 31 +#define taskq_t workq_t + +typedef struct workqueue_struct workq_t; +typedef unsigned long taskqid_t; +typedef void (*task_func_t)(void *); + +/* + * Public flags for taskq_create(): bit range 0-15 + */ +#define TASKQ_PREPOPULATE 0x0000 /* XXX - Workqueues fully populate */ +#define TASKQ_CPR_SAFE 0x0000 /* XXX - No analog */ +#define TASKQ_DYNAMIC 0x0000 /* XXX - Worksqueues not dynamic */ + +/* + * Flags for taskq_dispatch. TQ_SLEEP/TQ_NOSLEEP should be same as + * KM_SLEEP/KM_NOSLEEP. + */ +#define TQ_SLEEP 0x00 /* XXX - Workqueues don't support */ +#define TQ_NOSLEEP 0x00 /* these sorts of flags. They */ +#define TQ_NOQUEUE 0x00 /* always run in application */ +#define TQ_NOALLOC 0x00 /* context and can sleep. */ + + +#ifdef DEBUG_TASKQ_UNIMPLEMENTED +static __inline__ void taskq_init(void) { +#error "taskq_init() not implemented" +} + +static __inline__ taskq_t * +taskq_create_instance(const char *, int, int, pri_t, int, int, uint_t) { +#error "taskq_create_instance() not implemented" +} + +extern void nulltask(void *); +extern void taskq_suspend(taskq_t *); +extern int taskq_suspended(taskq_t *); +extern void taskq_resume(taskq_t *); + +#endif /* DEBUG_TASKQ_UNIMPLEMENTED */ + +extern taskqid_t __taskq_dispatch(taskq_t *, task_func_t, void *, uint_t); +extern taskq_t *__taskq_create(const char *, int, pri_t, int, int, uint_t); + +#define taskq_create(name, thr, pri, min, max, flags) \ + __taskq_create(name, thr, pri, min, max, flags) +#define taskq_dispatch(tq, func, priv, flags) \ + __taskq_dispatch(tq, func, priv, flags) +#define taskq_destory(tq) destroy_workqueue(tq) +#define taskq_wait(tq) flush_workqueue(tq) +#define taskq_member(tq, kthr) 1 /* XXX -Just be true */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_TASKQ_H */ diff --git a/include/linux-thread.h b/include/linux-thread.h new file mode 100644 index 000000000..dc9726322 --- /dev/null +++ b/include/linux-thread.h @@ -0,0 +1,48 @@ +#ifndef _SYS_LINUX_THREAD_H +#define _SYS_LINUX_THREAD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/mm.h> +#include <linux/spinlock.h> +#include <sys/linux-types.h> +#include <sys/linux-generic.h> + +/* + * Thread interfaces + */ +#define TP_MAGIC 0x53535353 + +#define TS_SLEEP TASK_INTERRUPTIBLE +#define TS_RUN TASK_RUNNING +#define TS_ZOMB EXIT_ZOMBIE +#define TS_STOPPED TASK_STOPPED +#if 0 +#define TS_FREE 0x00 /* No clean linux mapping */ +#define TS_ONPROC 0x04 /* No clean linux mapping */ +#define TS_WAIT 0x20 /* No clean linux mapping */ +#endif + +#define thread_create(stk, stksize, func, arg, len, pp, state, pri) \ + __thread_create(stk, stksize, func, arg, len, pp, state, pri) +#define thread_exit() __thread_exit() +#define curthread get_current() + +/* We just need a valid type to pass around, it's unused */ +typedef struct proc_s { + int foo; +} proc_t; + +kthread_t * __thread_create(caddr_t stk, size_t stksize, + void (*proc)(void *), void *args, + size_t len, proc_t *pp, int state, + pri_t pri); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_THREAD_H */ + diff --git a/include/linux-time.h b/include/linux-time.h new file mode 100644 index 000000000..c1105ab6b --- /dev/null +++ b/include/linux-time.h @@ -0,0 +1,64 @@ +#ifndef _SYS_TIME_H +#define _SYS_TIME_H + +#pragma ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.16 */ + +/* + * Structure returned by gettimeofday(2) system call, + * and used in other calls. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/time.h> +#include <sys/linux-types.h> + +extern unsigned long long monotonic_clock(void); +typedef struct timespec timestruc_t; /* definition per SVr4 */ +typedef longlong_t hrtime_t; + +#define TIME32_MAX INT32_MAX +#define TIME32_MIN INT32_MIN + +#define SEC 1 +#define MILLISEC 1000 +#define MICROSEC 1000000 +#define NANOSEC 1000000000 + +#define hz \ +({ \ + BUG_ON(HZ < 100 || HZ > MICROSEC); \ + HZ; \ +}) + +#define gethrestime(ts) getnstimeofday((ts)) + +static __inline__ hrtime_t +gethrtime(void) { + /* BUG_ON(cur_timer == timer_none); */ + + /* Solaris expects a long long here but monotonic_clock() returns an + * unsigned long long. Note that monotonic_clock() returns the number + * of nanoseconds passed since kernel initialization. Even for a signed + * long long this will not "go negative" for ~292 years. + */ + return monotonic_clock(); +} + +static __inline__ time_t +gethrestime_sec(void) +{ + timestruc_t now; + + gethrestime(&now); + return (now.tv_sec); +} + + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_TIME_H */ diff --git a/include/linux-timer.h b/include/linux-timer.h new file mode 100644 index 000000000..a01579519 --- /dev/null +++ b/include/linux-timer.h @@ -0,0 +1,21 @@ +#ifndef _SYS_LINUX_TIMER_H +#define _SYS_LINUX_TIMER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <linux/sched.h> +#include <linux/timer.h> + +#define lbolt ((clock_t)jiffies) +#define lbolt64 ((int64_t)get_jiffies_64()) + +#define delay(ticks) schedule_timeout((long timeout)(ticks)) + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_TIMER_H */ + diff --git a/include/linux-types.h b/include/linux-types.h new file mode 100644 index 000000000..d2b12a16b --- /dev/null +++ b/include/linux-types.h @@ -0,0 +1,27 @@ +#ifndef _SYS_LINUX_TYPES_H +#define _SYS_LINUX_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { B_FALSE=0, B_TRUE=1 } boolean_t; +typedef unsigned long uintptr_t; +typedef unsigned long intptr_t; +typedef unsigned long ulong_t; +typedef unsigned int uint_t; +typedef unsigned char uchar_t; +typedef unsigned long long u_longlong_t; +typedef unsigned long long u_offset_t; +typedef unsigned long long rlim64_t; +typedef long long longlong_t; +typedef long long offset_t; +typedef struct task_struct kthread_t; +typedef struct vmem { } vmem_t; +typedef short pri_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_LINUX_TYPES_H */ diff --git a/include/spl.h b/include/spl.h new file mode 100644 index 000000000..ba38e8105 --- /dev/null +++ b/include/spl.h @@ -0,0 +1,4 @@ +#ifndef _SYS_SPL_H +#define _SYS_SPL_H + +#endif /* _SYS_SPL_H */ diff --git a/include/splat-ctl.h b/include/splat-ctl.h new file mode 100644 index 000000000..b0e2a69eb --- /dev/null +++ b/include/splat-ctl.h @@ -0,0 +1,211 @@ +#ifndef _SYS_KZT_H +#define _SYS_KZT_H + +#ifdef _KERNEL +#include <asm/ioctls.h> +#include <asm/uaccess.h> +#include <linux/list.h> +#endif /* _KERNEL */ + +#define KZT_VERSION "v1.0" +#define KZT_VERSION_SIZE 64 + +#define KZT_MAJOR 229 /* XXX - Arbitrary */ +#define KZT_MINORS 1 +#define KZT_DEV "/dev/kztctl" + +#define KZT_NAME_SIZE 12 +#define KZT_DESC_SIZE 60 + +typedef struct kzt_user { + char name[KZT_NAME_SIZE]; /* short name */ + char desc[KZT_DESC_SIZE]; /* short description */ + int id; /* unique numeric id */ +} kzt_user_t; + +#define KZT_CFG_MAGIC 0x15263748U +typedef struct kzt_cfg { + unsigned int cfg_magic; /* Unique magic */ + int cfg_cmd; /* Config command */ + int cfg_arg1; /* Config command arg 1 */ + int cfg_rc1; /* Config response 1 */ + union { + struct { + int size; + kzt_user_t descs[0]; + } kzt_subsystems; + struct { + int size; + kzt_user_t descs[0]; + } kzt_tests; + } cfg_data; +} kzt_cfg_t; + +#define KZT_CMD_MAGIC 0x9daebfc0U +typedef struct kzt_cmd { + unsigned int cmd_magic; /* Unique magic */ + int cmd_subsystem; /* Target subsystem */ + int cmd_test; /* Subsystem test */ + int cmd_data_size; /* Extra opaque data */ + char cmd_data_str[0]; /* Opaque data region */ +} kzt_cmd_t; + +/* Valid ioctls */ +#define KZT_CFG _IOWR('f', 101, long) +#define KZT_CMD _IOWR('f', 102, long) + +/* Valid configuration commands */ +#define KZT_CFG_BUFFER_CLEAR 0x001 /* Clear text buffer */ +#define KZT_CFG_BUFFER_SIZE 0x002 /* Resize text buffer */ +#define KZT_CFG_SUBSYSTEM_COUNT 0x101 /* Number of subsystem */ +#define KZT_CFG_SUBSYSTEM_LIST 0x102 /* List of N subsystems */ +#define KZT_CFG_TEST_COUNT 0x201 /* Number of tests */ +#define KZT_CFG_TEST_LIST 0x202 /* List of N tests */ + +/* Valid subsystem and test commands defined in each subsystem, we do + * need to be careful to avoid colisions. That alone may argue to define + * them all here, for now we just define the global error codes. + */ +#define KZT_SUBSYSTEM_UNKNOWN 0xF00 +#define KZT_TEST_UNKNOWN 0xFFF + + +#ifdef _KERNEL +#define KZT_SUBSYSTEM_INIT(type) \ +({ kzt_subsystem_t *_sub_; \ + \ + _sub_ = (kzt_subsystem_t *)kzt_##type##_init(); \ + if (_sub_ == NULL) { \ + printk(KERN_ERR "Error initializing: " #type "\n"); \ + } else { \ + spin_lock(&kzt_module_lock); \ + list_add_tail(&(_sub_->subsystem_list), \ + &kzt_module_list); \ + spin_unlock(&kzt_module_lock); \ + } \ +}) + +#define KZT_SUBSYSTEM_FINI(type) \ +({ kzt_subsystem_t *_sub_, *_tmp_; \ + int _id_, _flag_ = 0; \ + \ + _id_ = kzt_##type##_id(); \ + spin_lock(&kzt_module_lock); \ + list_for_each_entry_safe(_sub_, _tmp_, &kzt_module_list, \ + subsystem_list) { \ + if (_sub_->desc.id == _id_) { \ + list_del_init(&(_sub_->subsystem_list)); \ + spin_unlock(&kzt_module_lock); \ + kzt_##type##_fini(_sub_); \ + spin_lock(&kzt_module_lock); \ + _flag_ = 1; \ + } \ + } \ + spin_unlock(&kzt_module_lock); \ + \ + if (!_flag_) \ + printk(KERN_ERR "Error finalizing: " #type "\n"); \ +}) + +#define KZT_TEST_INIT(sub, n, d, tid, func) \ +({ kzt_test_t *_test_; \ + \ + _test_ = (kzt_test_t *)kmalloc(sizeof(*_test_), GFP_KERNEL); \ + if (_test_ == NULL) { \ + printk(KERN_ERR "Error initializing: " n "/" #tid" \n");\ + } else { \ + memset(_test_, 0, sizeof(*_test_)); \ + strncpy(_test_->desc.name, n, KZT_NAME_SIZE); \ + strncpy(_test_->desc.desc, d, KZT_DESC_SIZE); \ + _test_->desc.id = tid; \ + _test_->test = func; \ + INIT_LIST_HEAD(&(_test_->test_list)); \ + spin_lock(&((sub)->test_lock)); \ + list_add_tail(&(_test_->test_list),&((sub)->test_list));\ + spin_unlock(&((sub)->test_lock)); \ + } \ +}) + +#define KZT_TEST_FINI(sub, tid) \ +({ kzt_test_t *_test_, *_tmp_; \ + int _flag_ = 0; \ + \ + spin_lock(&((sub)->test_lock)); \ + list_for_each_entry_safe(_test_, _tmp_, \ + &((sub)->test_list), test_list) { \ + if (_test_->desc.id == tid) { \ + list_del_init(&(_test_->test_list)); \ + _flag_ = 1; \ + } \ + } \ + spin_unlock(&((sub)->test_lock)); \ + \ + if (!_flag_) \ + printk(KERN_ERR "Error finalizing: " #tid "\n"); \ +}) + +typedef int (*kzt_test_func_t)(struct file *, void *); + +typedef struct kzt_test { + struct list_head test_list; + kzt_user_t desc; + kzt_test_func_t test; +} kzt_test_t; + +typedef struct kzt_subsystem { + struct list_head subsystem_list;/* List had to chain entries */ + kzt_user_t desc; + spinlock_t test_lock; + struct list_head test_list; +} kzt_subsystem_t; + +#define KZT_INFO_BUFFER_SIZE 65536 +#define KZT_INFO_BUFFER_REDZONE 256 + +typedef struct kzt_info { + spinlock_t info_lock; + int info_size; + char *info_buffer; + char *info_head; /* Internal kernel use only */ +} kzt_info_t; + +#define sym2str(sym) (char *)(#sym) + +#define kzt_print(file, format, args...) \ +({ kzt_info_t *_info_ = (kzt_info_t *)file->private_data; \ + int _rc_; \ + \ + ASSERT(_info_); \ + ASSERT(_info_->info_buffer); \ + \ + spin_lock(&_info_->info_lock); \ + \ + /* Don't allow the kernel to start a write in the red zone */ \ + if ((int)(_info_->info_head - _info_->info_buffer) > \ + (KZT_INFO_BUFFER_SIZE -KZT_INFO_BUFFER_REDZONE)) { \ + _rc_ = -EOVERFLOW; \ + } else { \ + _rc_ = sprintf(_info_->info_head, format, args); \ + if (_rc_ >= 0) \ + _info_->info_head += _rc_; \ + } \ + \ + spin_unlock(&_info_->info_lock); \ + _rc_; \ +}) + +#define kzt_vprint(file, test, format, args...) \ + kzt_print(file, "%*s: " format, KZT_NAME_SIZE, test, args) + +kzt_subsystem_t * kzt_condvar_init(void); +kzt_subsystem_t * kzt_kmem_init(void); +kzt_subsystem_t * kzt_mutex_init(void); +kzt_subsystem_t * kzt_krng_init(void); +kzt_subsystem_t * kzt_rwlock_init(void); +kzt_subsystem_t * kzt_taskq_init(void); +kzt_subsystem_t * kzt_thread_init(void); +kzt_subsystem_t * kzt_time_init(void); + +#endif /* _KERNEL */ + +#endif /* _SYS_KZT_H */ diff --git a/include/splat.h b/include/splat.h new file mode 100644 index 000000000..69cb387ad --- /dev/null +++ b/include/splat.h @@ -0,0 +1,47 @@ +#ifndef _KZT_H +#define _KZT_H + +#include <sys/splat-ctl.h> + +#define DEV_NAME "/dev/kztctl" +#define COLOR_BLACK "\033[0;30m" +#define COLOR_DK_GRAY "\033[1;30m" +#define COLOR_BLUE "\033[0;34m" +#define COLOR_LT_BLUE "\033[1;34m" +#define COLOR_GREEN "\033[0;32m" +#define COLOR_LT_GREEN "\033[1;32m" +#define COLOR_CYAN "\033[0;36m" +#define COLOR_LT_CYAN "\033[1;36m" +#define COLOR_RED "\033[0;31m" +#define COLOR_LT_RED "\033[1;31m" +#define COLOR_PURPLE "\033[0;35m" +#define COLOR_LT_PURPLE "\033[1;35m" +#define COLOR_BROWN "\033[0;33m" +#define COLOR_YELLOW "\033[1;33m" +#define COLOR_LT_GRAY "\033[0;37m" +#define COLOR_WHITE "\033[1;37m" +#define COLOR_RESET "\033[0m" + +typedef struct subsystem { + uu_list_node_t sub_node; /* Linkage for global subsystem list */ + kzt_user_t sub_desc; /* Subsystem description */ + uu_list_t *sub_tests; /* Assocated subsystem tests list */ +} subsystem_t; + +typedef struct test { + uu_list_node_t test_node; /* Linkage for globals test list */ + kzt_user_t test_desc; /* Test description */ + subsystem_t *test_sub; /* Parent subsystem */ +} test_t; + +typedef struct cmd_args { + int args_verbose; /* Verbose flag */ + int args_do_list; /* Display all tests flag */ + int args_do_all; /* Run all tests flag */ + int args_do_color; /* Colorize output */ + int args_exit_on_error; /* Exit on first error flag */ + uu_list_t *args_tests; /* Requested subsystems/tests */ +} cmd_args_t; + +#endif /* _KZT_H */ + diff --git a/scripts/Makefile.am b/scripts/Makefile.am new file mode 100644 index 000000000..1c92f977b --- /dev/null +++ b/scripts/Makefile.am @@ -0,0 +1 @@ +EXTRA_DIST = spl.spec.in diff --git a/scripts/spl.spec b/scripts/spl.spec new file mode 100644 index 000000000..1ce524865 --- /dev/null +++ b/scripts/spl.spec @@ -0,0 +1,28 @@ +# spl +%define name spl +%define version 0.0.1 + +Summary: Solaris Porting Layer +Name: %{name} +Version: %{version} +Release: 1 +Copyright: GPL +Group: Utilities/System +BuildRoot: /tmp/%{name}-%{version} +Source: %{name}-%{version}.tar.gz + +%description +Abstration layer to provide Solaris style primatives in the linux kernel. + +%prep +%setup -q +./configure + +%build +rm -rf $RPM_BUILD_ROOT +make + +%install +make install "DESTDIR=$RPM_BUILD_ROOT" + +%files diff --git a/scripts/spl.spec.in b/scripts/spl.spec.in new file mode 100644 index 000000000..9d9d4e325 --- /dev/null +++ b/scripts/spl.spec.in @@ -0,0 +1,28 @@ +# spl +%define name @PACKAGE@ +%define version @VERSION@ + +Summary: Solaris Porting Layer +Name: %{name} +Version: %{version} +Release: 1 +Copyright: GPL +Group: Utilities/System +BuildRoot: /tmp/%{name}-%{version} +Source: %{name}-%{version}.tar.gz + +%description +Abstration layer to provide Solaris style primatives in the linux kernel. + +%prep +%setup -q +./configure + +%build +rm -rf $RPM_BUILD_ROOT +make + +%install +make install "DESTDIR=$RPM_BUILD_ROOT" + +%files diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 000000000..86f519112 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = cmd spl splat diff --git a/src/cmd/Makefile.am b/src/cmd/Makefile.am new file mode 100644 index 000000000..2ab0a497c --- /dev/null +++ b/src/cmd/Makefile.am @@ -0,0 +1,3 @@ +AM_CFLAGS = @EXTRA_CFLAGS@ -g -O2 -W -Wall -Wstrict-prototypes -Wshadow +sbin_PROGRAMS = splat +kzt_SOURCES = splat.c diff --git a/src/cmd/splat.c b/src/cmd/splat.c new file mode 100644 index 000000000..0ad65490c --- /dev/null +++ b/src/cmd/splat.c @@ -0,0 +1,821 @@ +/* Kernel ZFS Test (KZT) user space command interface */ + +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> +#include <assert.h> +#include <fcntl.h> +#include <libuutil.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include "splat.h" + +#undef ioctl + +static const char shortOpts[] = "hvlat:xc"; +static const struct option longOpts[] = { + { "help", no_argument, 0, 'h' }, + { "verbose", no_argument, 0, 'v' }, + { "list", no_argument, 0, 'l' }, + { "all", no_argument, 0, 'a' }, + { "test", required_argument, 0, 't' }, + { "exit", no_argument, 0, 'x' }, + { "nocolor", no_argument, 0, 'c' }, + { 0, 0, 0, 0 } +}; + +static uu_list_t *subsystems; /* Subsystem/tests */ +static uu_list_pool_t *subsystem_pool; /* Subsystem pool */ +static uu_list_pool_t *test_pool; /* Test pool */ +static int kztctl_fd; /* Control file descriptor */ +static char kzt_version[KZT_VERSION_SIZE]; /* Kernel version string */ +static char *kzt_buffer = NULL; /* Scratch space area */ +static int kzt_buffer_size = 0; /* Scratch space size */ + + +static void test_list(uu_list_t *, int); +static int dev_clear(void); + + +static int usage(void) { + fprintf(stderr, "usage: kzt [hvla] [-t <subsystem:<tests>>]\n"); + fprintf(stderr, + " --help -h This help\n" + " --verbose -v Increase verbosity\n" + " --list -l List all tests in all subsystems\n" + " --all -a Run all tests in all subsystems\n" + " --test -t <sub:test> Run 'test' in subsystem 'sub'\n" + " --exit -x Exit on first test error\n" + " --nocolor -c Do not colorize output\n"); + fprintf(stderr, "\n" + "Examples:\n" + " kzt -t kmem:all # Runs all kmem tests\n" + " kzt -t taskq:0x201 # Run taskq test 0x201\n"); + + return 0; +} + +static subsystem_t *subsystem_init(kzt_user_t *desc) +{ + subsystem_t *sub; + + sub = (subsystem_t *)malloc(sizeof(*sub)); + if (sub == NULL) + return NULL; + + memcpy(&sub->sub_desc, desc, sizeof(*desc)); + uu_list_node_init(sub, &sub->sub_node, subsystem_pool); + + sub->sub_tests = uu_list_create(test_pool, NULL, 0); + if (sub->sub_tests == NULL) { + free(sub); + return NULL; + } + + return sub; +} + +static void subsystem_fini(subsystem_t *sub) +{ + assert(sub != NULL); + + uu_list_node_fini(sub, &sub->sub_node, subsystem_pool); + free(sub); +} + +static int subsystem_setup(void) +{ + kzt_cfg_t *cfg; + int i, rc, size, cfg_size; + subsystem_t *sub; + kzt_user_t *desc; + + /* Aquire the number of registered subsystems */ + cfg_size = sizeof(*cfg); + cfg = (kzt_cfg_t *)malloc(cfg_size); + if (cfg == NULL) + return -ENOMEM; + + memset(cfg, 0, cfg_size); + cfg->cfg_magic = KZT_CFG_MAGIC; + cfg->cfg_cmd = KZT_CFG_SUBSYSTEM_COUNT; + + rc = ioctl(kztctl_fd, KZT_CFG, cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg->cfg_cmd, errno); + free(cfg); + return rc; + } + + size = cfg->cfg_rc1; + free(cfg); + + /* Based on the newly aquired number of subsystems allocate enough + * memory to get the descriptive information for them all. */ + cfg_size = sizeof(*cfg) + size * sizeof(kzt_user_t); + cfg = (kzt_cfg_t *)malloc(cfg_size); + if (cfg == NULL) + return -ENOMEM; + + memset(cfg, 0, cfg_size); + cfg->cfg_magic = KZT_CFG_MAGIC; + cfg->cfg_cmd = KZT_CFG_SUBSYSTEM_LIST; + cfg->cfg_data.kzt_subsystems.size = size; + + rc = ioctl(kztctl_fd, KZT_CFG, cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg->cfg_cmd, errno); + free(cfg); + return rc; + } + + /* Add the new subsystems in to the global list */ + size = cfg->cfg_rc1; + for (i = 0; i < size; i++) { + desc = &(cfg->cfg_data.kzt_subsystems.descs[i]); + + sub = subsystem_init(desc); + if (sub == NULL) { + fprintf(stderr, "Error initializing subsystem: %s\n", + desc->name); + free(cfg); + return -ENOMEM; + } + + uu_list_insert(subsystems, sub, 0); + } + + free(cfg); + return 0; +} + +static int subsystem_compare(const void *l_arg, const void *r_arg, void *private) +{ + const subsystem_t *l = l_arg; + const subsystem_t *r = r_arg; + + if (l->sub_desc.id > r->sub_desc.id) + return 1; + + if (l->sub_desc.id < r->sub_desc.id) + return -1; + + return 0; +} + +static void subsystem_list(uu_list_t *list, int indent) +{ + subsystem_t *sub; + + fprintf(stdout, + "------------------------------- " + "Available KZT Tests " + "-------------------------------\n"); + + for (sub = uu_list_first(list); sub != NULL; + sub = uu_list_next(list, sub)) { + fprintf(stdout, "%*s0x%0*x %-*s ---- %s ----\n", + indent, "", + 4, sub->sub_desc.id, + KZT_NAME_SIZE + 7, sub->sub_desc.name, + sub->sub_desc.desc); + test_list(sub->sub_tests, indent + 7); + } +} + +static test_t *test_init(subsystem_t *sub, kzt_user_t *desc) +{ + test_t *test; + + test = (test_t *)malloc(sizeof(*test)); + if (test == NULL) + return NULL; + + test->test_sub = sub; + memcpy(&test->test_desc, desc, sizeof(*desc)); + uu_list_node_init(test, &test->test_node, test_pool); + + return test; +} + +static void test_fini(test_t *test) +{ + assert(test != NULL); + + uu_list_node_fini(test, &test->test_node, test_pool); + free(test); +} + +static int test_setup(subsystem_t *sub) +{ + kzt_cfg_t *cfg; + int i, rc, size; + test_t *test; + kzt_user_t *desc; + + /* Aquire the number of registered tests for the give subsystem */ + cfg = (kzt_cfg_t *)malloc(sizeof(*cfg)); + if (cfg == NULL) + return -ENOMEM; + + memset(cfg, 0, sizeof(*cfg)); + cfg->cfg_magic = KZT_CFG_MAGIC; + cfg->cfg_cmd = KZT_CFG_TEST_COUNT; + cfg->cfg_arg1 = sub->sub_desc.id; /* Subsystem of interest */ + + rc = ioctl(kztctl_fd, KZT_CFG, cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg->cfg_cmd, errno); + free(cfg); + return rc; + } + + size = cfg->cfg_rc1; + free(cfg); + + /* Based on the newly aquired number of tests allocate enough + * memory to get the descriptive information for them all. */ + cfg = (kzt_cfg_t *)malloc(sizeof(*cfg) + size * sizeof(kzt_user_t)); + if (cfg == NULL) + return -ENOMEM; + + memset(cfg, 0, sizeof(*cfg) + size * sizeof(kzt_user_t)); + cfg->cfg_magic = KZT_CFG_MAGIC; + cfg->cfg_cmd = KZT_CFG_TEST_LIST; + cfg->cfg_arg1 = sub->sub_desc.id; /* Subsystem of interest */ + cfg->cfg_data.kzt_tests.size = size; + + rc = ioctl(kztctl_fd, KZT_CFG, cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg->cfg_cmd, errno); + free(cfg); + return rc; + } + + /* Add the new tests in to the relevant subsystems */ + size = cfg->cfg_rc1; + for (i = 0; i < size; i++) { + desc = &(cfg->cfg_data.kzt_tests.descs[i]); + + test = test_init(sub, desc); + if (test == NULL) { + fprintf(stderr, "Error initializing test: %s\n", + desc->name); + free(cfg); + return -ENOMEM; + } + + uu_list_insert(sub->sub_tests, test, 0); + } + + free(cfg); + return 0; +} + +static int test_compare(const void *l_arg, const void *r_arg, void *private) +{ + const test_t *l = l_arg; + const test_t *r = r_arg; + + if (l->test_desc.id > r->test_desc.id) + return 1; + + if (l->test_desc.id < r->test_desc.id) + return -1; + + return 0; +} + +static test_t *test_copy(test_t *test) +{ + return test_init(test->test_sub, &test->test_desc); +} + +static void test_list(uu_list_t *list, int indent) +{ + test_t *test; + + for (test = uu_list_first(list); test != NULL; + test = uu_list_next(list, test)) + fprintf(stdout, "%*s0x%0*x %-*s %-*s\n", + indent, "", + 04, test->test_desc.id, + KZT_NAME_SIZE, test->test_desc.name, + KZT_DESC_SIZE, test->test_desc.desc); +} + +static test_t *test_find(char *sub_str, char *test_str) +{ + subsystem_t *sub; + test_t *test; + int sub_num, test_num; + + /* No error checking here because it may not be a number, it's + * perfectly OK for it to be a string. Since we're just using + * it for comparison purposes this is all very safe. + */ + sub_num = strtol(sub_str, NULL, 0); + test_num = strtol(test_str, NULL, 0); + + for (sub = uu_list_first(subsystems); sub != NULL; + sub = uu_list_next(subsystems, sub)) { + + if (strncmp(sub->sub_desc.name, sub_str, KZT_NAME_SIZE) && + sub->sub_desc.id != sub_num) + continue; + + for (test = uu_list_first(sub->sub_tests); test != NULL; + test = uu_list_next(sub->sub_tests, test)) { + + if (!strncmp(test->test_desc.name, test_str, + KZT_NAME_SIZE) || test->test_desc.id == test_num) + return test; + } + } + + return NULL; +} + +static int test_add(cmd_args_t *args, test_t *test) +{ + test_t *tmp; + + tmp = test_copy(test); + if (tmp == NULL) + return -ENOMEM; + + uu_list_insert(args->args_tests, tmp, 0); + return 0; +} + +static int test_add_all(cmd_args_t *args) +{ + subsystem_t *sub; + test_t *test; + int rc; + + for (sub = uu_list_first(subsystems); sub != NULL; + sub = uu_list_next(subsystems, sub)) { + + for (test = uu_list_first(sub->sub_tests); test != NULL; + test = uu_list_next(sub->sub_tests, test)) { + + if (rc = test_add(args, test)) + return rc; + } + } + + return 0; +} + +static int test_run(cmd_args_t *args, test_t *test) +{ + subsystem_t *sub = test->test_sub; + kzt_cmd_t *cmd; + int rc, cmd_size; + + dev_clear(); + + cmd_size = sizeof(*cmd); + cmd = (kzt_cmd_t *)malloc(cmd_size); + if (cmd == NULL) + return -ENOMEM; + + memset(cmd, 0, cmd_size); + cmd->cmd_magic = KZT_CMD_MAGIC; + cmd->cmd_subsystem = sub->sub_desc.id; + cmd->cmd_test = test->test_desc.id; + cmd->cmd_data_size = 0; /* Unused feature */ + + fprintf(stdout, "%*s:%-*s ", + KZT_NAME_SIZE, sub->sub_desc.name, + KZT_NAME_SIZE, test->test_desc.name); + fflush(stdout); + rc = ioctl(kztctl_fd, KZT_CMD, cmd); + if (args->args_do_color) { + fprintf(stdout, "%s %s\n", rc ? + COLOR_RED "Fail" COLOR_RESET : + COLOR_GREEN "Pass" COLOR_RESET, + rc ? strerror(errno) : ""); + } else { + fprintf(stdout, "%s %s\n", rc ? + "Fail" : "Pass", + rc ? strerror(errno) : ""); + } + fflush(stdout); + free(cmd); + + if (args->args_verbose) { + if ((rc = read(kztctl_fd, kzt_buffer, kzt_buffer_size - 1)) < 0) { + fprintf(stdout, "Error reading results: %d\n", rc); + } else { + fprintf(stdout, "\n%s\n", kzt_buffer); + fflush(stdout); + } + } + + return rc; +} + +static int tests_run(cmd_args_t *args) +{ + test_t *test; + int rc; + + fprintf(stdout, + "------------------------------- " + "Running KZT Tests " + "-------------------------------\n"); + + for (test = uu_list_first(args->args_tests); test != NULL; + test = uu_list_next(args->args_tests, test)) { + + rc = test_run(args, test); + if (rc && args->args_exit_on_error) + return rc; + } + + return 0; +} + +static int args_parse_test(cmd_args_t *args, char *str) +{ + subsystem_t *s; + test_t *t; + char *sub_str, *test_str; + int sub_num, test_num; + int sub_all = 0, test_all = 0; + int rc, flag = 0; + + test_str = strchr(str, ':'); + if (test_str == NULL) { + fprintf(stderr, "Test must be of the " + "form <subsystem:test>\n"); + return -EINVAL; + } + + sub_str = str; + test_str[0] = '\0'; + test_str = test_str + 1; + + sub_num = strtol(sub_str, NULL, 0); + test_num = strtol(test_str, NULL, 0); + + if (!strncasecmp(sub_str, "all", strlen(sub_str)) || (sub_num == -1)) + sub_all = 1; + + if (!strncasecmp(test_str, "all", strlen(test_str)) || (test_num == -1)) + test_all = 1; + + if (sub_all) { + if (test_all) { + /* Add all tests from all subsystems */ + for (s = uu_list_first(subsystems); s != NULL; + s = uu_list_next(subsystems, s)) + for (t = uu_list_first(s->sub_tests);t != NULL; + t = uu_list_next(s->sub_tests, t)) + if (rc = test_add(args, t)) + goto error_run; + } else { + /* Add a specific test from all subsystems */ + for (s = uu_list_first(subsystems); s != NULL; + s = uu_list_next(subsystems, s)) { + if (t = test_find(s->sub_desc.name,test_str)) { + if (rc = test_add(args, t)) + goto error_run; + + flag = 1; + } + } + + if (!flag) + fprintf(stderr, "No tests '%s:%s' could be " + "found\n", sub_str, test_str); + } + } else { + if (test_all) { + /* Add all tests from a specific subsystem */ + for (s = uu_list_first(subsystems); s != NULL; + s = uu_list_next(subsystems, s)) { + if (strncasecmp(sub_str, s->sub_desc.name, + strlen(sub_str))) + continue; + + for (t = uu_list_first(s->sub_tests);t != NULL; + t = uu_list_next(s->sub_tests, t)) + if (rc = test_add(args, t)) + goto error_run; + } + } else { + /* Add a specific test from a specific subsystem */ + if (t = test_find(sub_str, test_str)) { + if (rc = test_add(args, t)) + goto error_run; + } else { + fprintf(stderr, "Test '%s:%s' could not be " + "found\n", sub_str, test_str); + return -EINVAL; + } + } + } + + return 0; + +error_run: + fprintf(stderr, "Test '%s:%s' not added to run list: %d\n", + sub_str, test_str, rc); + return rc; +} + +static void args_fini(cmd_args_t *args) +{ + struct cmd_test *ptr1, *ptr2; + + assert(args != NULL); + + + + if (args->args_tests != NULL) { + uu_list_destroy(args->args_tests); + } + + free(args); +} + +static cmd_args_t * +args_init(int argc, char **argv) +{ + cmd_args_t *args; + int c, rc; + + if (argc == 1) { + usage(); + return (cmd_args_t *) NULL; + } + + /* Configure and populate the args structures */ + args = malloc(sizeof(*args)); + if (args == NULL) + return NULL; + + memset(args, 0, sizeof(*args)); + args->args_verbose = 0; + args->args_do_list = 0; + args->args_do_all = 0; + args->args_do_color = 1; + args->args_exit_on_error = 0; + args->args_tests = uu_list_create(test_pool, NULL, 0); + if (args->args_tests == NULL) { + args_fini(args); + return NULL; + } + + while ((c = getopt_long(argc, argv, shortOpts, longOpts, NULL)) != -1){ + switch (c) { + case 'v': args->args_verbose++; break; + case 'l': args->args_do_list = 1; break; + case 'a': args->args_do_all = 1; break; + case 'c': args->args_do_color = 0; break; + case 'x': args->args_exit_on_error = 1; break; + case 't': + if (args->args_do_all) { + fprintf(stderr, "Option -t <subsystem:test> is " + "useless when used with -a\n"); + args_fini(args); + return NULL; + } + + rc = args_parse_test(args, argv[optind - 1]); + if (rc) { + args_fini(args); + return NULL; + } + break; + case 'h': + case '?': + usage(); + args_fini(args); + return NULL; + default: + fprintf(stderr, "Unknown option '%s'\n", + argv[optind - 1]); + break; + } + } + + return args; +} + +static int +dev_clear(void) +{ + kzt_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = KZT_CFG_MAGIC; + cfg.cfg_cmd = KZT_CFG_BUFFER_CLEAR; + cfg.cfg_arg1 = 0; + + rc = ioctl(kztctl_fd, KZT_CFG, &cfg); + if (rc) + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg.cfg_cmd, errno); + + lseek(kztctl_fd, 0, SEEK_SET); + + return rc; +} + +static int +dev_size(int size) +{ + kzt_cfg_t cfg; + int rc; + + memset(&cfg, 0, sizeof(cfg)); + cfg.cfg_magic = KZT_CFG_MAGIC; + cfg.cfg_cmd = KZT_CFG_BUFFER_SIZE; + cfg.cfg_arg1 = size; + + rc = ioctl(kztctl_fd, KZT_CFG, &cfg); + if (rc) { + fprintf(stderr, "Ioctl() error %lu / %d: %d\n", + (unsigned long) KZT_CFG, cfg.cfg_cmd, errno); + return rc; + } + + return cfg.cfg_rc1; +} + +static void +dev_fini(void) +{ + if (kzt_buffer) + free(kzt_buffer); + + if (kztctl_fd != -1) { + if (close(kztctl_fd) == -1) { + fprintf(stderr, "Unable to close %s: %d\n", + KZT_DEV, errno); + } + } +} + +static int +dev_init(void) +{ + subsystem_t *sub; + int rc; + + kztctl_fd = open(KZT_DEV, O_RDONLY); + if (kztctl_fd == -1) { + fprintf(stderr, "Unable to open %s: %d\n" + "Is the kzt module loaded?\n", KZT_DEV, errno); + rc = errno; + goto error; + } + + /* Determine kernel module version string */ + memset(kzt_version, 0, KZT_VERSION_SIZE); + if ((rc = read(kztctl_fd, kzt_version, KZT_VERSION_SIZE - 1)) == -1) + goto error; + + if (rc = dev_clear()) + goto error; + + if ((rc = dev_size(0)) < 0) + goto error; + + kzt_buffer_size = rc; + kzt_buffer = (char *)malloc(kzt_buffer_size); + if (kzt_buffer == NULL) { + rc = -ENOMEM; + goto error; + } + + memset(kzt_buffer, 0, kzt_buffer_size); + + /* Determine available subsystems */ + if ((rc = subsystem_setup()) != 0) + goto error; + + /* Determine available tests for all subsystems */ + for (sub = uu_list_first(subsystems); sub != NULL; + sub = uu_list_next(subsystems, sub)) + if ((rc = test_setup(sub)) != 0) + goto error; + + return 0; + +error: + if (kztctl_fd != -1) { + if (close(kztctl_fd) == -1) { + fprintf(stderr, "Unable to close %s: %d\n", + KZT_DEV, errno); + } + } + + return rc; +} + +int +init(void) +{ + int rc; + + /* Configure the subsystem pool */ + subsystem_pool = uu_list_pool_create("sub_pool", sizeof(subsystem_t), + offsetof(subsystem_t, sub_node), + subsystem_compare, 0); + if (subsystem_pool == NULL) + return -ENOMEM; + + /* Configure the test pool */ + test_pool = uu_list_pool_create("test_pool", sizeof(test_t), + offsetof(test_t, test_node), + test_compare, 0); + if (test_pool == NULL) { + uu_list_pool_destroy(subsystem_pool); + return -ENOMEM; + } + + /* Allocate the subsystem list */ + subsystems = uu_list_create(subsystem_pool, NULL, 0); + if (subsystems == NULL) { + uu_list_pool_destroy(test_pool); + uu_list_pool_destroy(subsystem_pool); + return -ENOMEM; + } + + return 0; +} + +void +fini(void) +{ + /* XXX - Cleanup destroy lists release memory */ + + /* XXX - Remove contents of list first */ + uu_list_destroy(subsystems); +} + + +int +main(int argc, char **argv) +{ + cmd_args_t *args = NULL; + int rc = 0; + + /* General init */ + if (rc = init()) + return rc; + + /* Device specific init */ + if (rc = dev_init()) + goto out; + + /* Argument init and parsing */ + if ((args = args_init(argc, argv)) == NULL) { + rc = -1; + goto out; + } + + /* Generic kernel version string */ + if (args->args_verbose) + fprintf(stdout, "%s", kzt_version); + + /* Print the available test list and exit */ + if (args->args_do_list) { + subsystem_list(subsystems, 0); + goto out; + } + + /* Add all available test to the list of tests to run */ + if (args->args_do_all) { + if (rc = test_add_all(args)) + goto out; + } + + /* Run all the requested tests */ + if (rc = tests_run(args)) + goto out; + +out: + if (args != NULL) + args_fini(args); + + dev_fini(); + fini(); + return rc; +} + diff --git a/src/spl/Makefile.in b/src/spl/Makefile.in new file mode 100644 index 000000000..d052fc370 --- /dev/null +++ b/src/spl/Makefile.in @@ -0,0 +1,50 @@ +# Makefile.in for spl kernel module + +MODULES := spl + +DISTFILES = Makefile.in \ + linux-kmem.c linux-rwlock.c linux-taskq.c linux-thread.c + +# Removed '-std=gnu99' does to compile issues with i386 SPIN_LOCK_UNLOCKED +# EXTRA_CFLAGS += -I$(src) +# EXTRA_CFLAGS += -Wall -Wno-unknown-pragmas -Wno-missing-braces \ +# -Wno-sign-compare -Wno-parentheses -Wno-uninitialized \ +# -Wno-implicit-function-declaration -Wno-unused -Wno-trigraphs \ +# -Wno-char-subscripts -Wno-switch + +# Solaris porting layer module +obj-m := spl.o + +spl-objs += linux-kmem.o +spl-objs += linux-thread.o +spl-objs += linux-taskq.o +spl-objs += linux-rwlock.o + +splmodule := spl.ko +splmoduledir := @kmoduledir@/kernel/lib/ + +all: all-spec + +install: all + mkdir -p $(DESTDIR)$(splmoduledir) + $(INSTALL) -m 644 $(splmodule) $(DESTDIR)$(splmoduledir)/$(splmodule) + -/sbin/depmod -a + +uninstall: + rm -f $(DESTDIR)$(splmoduledir)/$(splmodule) + -/sbin/depmod -a + +clean: + -rm -f $(splmodule) *.o .*.cmd *.mod.c *.ko *.s */*.o + +distclean: clean + rm -f Makefile + rm -rf .tmp_versions + +maintainer-clean: distclean + +distdir: $(DISTFILES) + cp -p $(DISTFILES) $(distdir) + +all-spec: + $(MAKE) -C @kernelsrc@ SUBDIRS=`pwd` @KERNELMAKE_PARAMS@ modules diff --git a/src/spl/linux-kmem.c b/src/spl/linux-kmem.c new file mode 100644 index 000000000..7de2b211d --- /dev/null +++ b/src/spl/linux-kmem.c @@ -0,0 +1,249 @@ +#include <sys/linux-kmem.h> + +/* + * Memory allocation interfaces + */ +#ifdef DEBUG_KMEM +/* Shim layer memory accounting */ +atomic_t kmem_alloc_used; +unsigned int kmem_alloc_max; +#endif + +/* + * Slab allocation interfaces + * + * While the linux slab implementation was inspired by solaris they + * have made some changes to the API which complicates this shim + * layer. For one thing the same symbol names are used with different + * arguments for the prototypes. To deal with this we must use the + * preprocessor to re-order arguments. Happily for us standard C says, + * "Macro's appearing in their own expansion are not reexpanded" so + * this does not result in an infinite recursion. Additionally the + * function pointers registered by solarias differ from those used + * by linux so a lookup and mapping from linux style callback to a + * solaris style callback is needed. There is some overhead in this + * operation which isn't horibile but it needs to be kept in mind. + */ +typedef struct kmem_cache_cb { + struct list_head kcc_list; + kmem_cache_t * kcc_cache; + kmem_constructor_t kcc_constructor; + kmem_destructor_t kcc_destructor; + kmem_reclaim_t kcc_reclaim; + void * kcc_private; + void * kcc_vmp; +} kmem_cache_cb_t; + + +static spinlock_t kmem_cache_cb_lock = SPIN_LOCK_UNLOCKED; +//static spinlock_t kmem_cache_cb_lock = (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }; +static LIST_HEAD(kmem_cache_cb_list); +static struct shrinker *kmem_cache_shrinker; + +/* Function must be called while holding the kmem_cache_cb_lock + * Because kmem_cache_t is an opaque datatype we're forced to + * match pointers to identify specific cache entires. + */ +static kmem_cache_cb_t * +kmem_cache_find_cache_cb(kmem_cache_t *cache) +{ + kmem_cache_cb_t *kcc; + + list_for_each_entry(kcc, &kmem_cache_cb_list, kcc_list) + if (cache == kcc->kcc_cache) + return kcc; + + return NULL; +} + +static kmem_cache_cb_t * +kmem_cache_add_cache_cb(kmem_cache_t *cache, + kmem_constructor_t constructor, + kmem_destructor_t destructor, + kmem_reclaim_t reclaim, + void *priv, void *vmp) +{ + kmem_cache_cb_t *kcc; + + kcc = (kmem_cache_cb_t *)kmalloc(sizeof(*kcc), GFP_KERNEL); + if (kcc) { + kcc->kcc_cache = cache; + kcc->kcc_constructor = constructor; + kcc->kcc_destructor = destructor; + kcc->kcc_reclaim = reclaim; + kcc->kcc_private = priv; + kcc->kcc_vmp = vmp; + spin_lock(&kmem_cache_cb_lock); + list_add(&kcc->kcc_list, &kmem_cache_cb_list); + spin_unlock(&kmem_cache_cb_lock); + } + + return kcc; +} + +static void +kmem_cache_remove_cache_cb(kmem_cache_cb_t *kcc) +{ + spin_lock(&kmem_cache_cb_lock); + list_del(&kcc->kcc_list); + spin_unlock(&kmem_cache_cb_lock); + + if (kcc) + kfree(kcc); +} + +static void +kmem_cache_generic_constructor(void *ptr, kmem_cache_t *cache, unsigned long flags) +{ + kmem_cache_cb_t *kcc; + + spin_lock(&kmem_cache_cb_lock); + + /* Callback list must be in sync with linux slab caches */ + kcc = kmem_cache_find_cache_cb(cache); + BUG_ON(!kcc); + + kcc->kcc_constructor(ptr, kcc->kcc_private, (int)flags); + spin_unlock(&kmem_cache_cb_lock); + /* Linux constructor has no return code, silently eat it */ +} + +static void +kmem_cache_generic_destructor(void *ptr, kmem_cache_t *cache, unsigned long flags) +{ + kmem_cache_cb_t *kcc; + + spin_lock(&kmem_cache_cb_lock); + + /* Callback list must be in sync with linux slab caches */ + kcc = kmem_cache_find_cache_cb(cache); + BUG_ON(!kcc); + + /* Solaris destructor takes no flags, silently eat them */ + kcc->kcc_destructor(ptr, kcc->kcc_private); + spin_unlock(&kmem_cache_cb_lock); +} + +/* XXX - Arguments are ignored */ +static int +kmem_cache_generic_shrinker(int nr_to_scan, unsigned int gfp_mask) +{ + kmem_cache_cb_t *kcc; + int total = 0; + + /* Under linux a shrinker is not tightly coupled with a slab + * cache. In fact linux always systematically trys calling all + * registered shrinker callbacks until its target reclamation level + * is reached. Because of this we only register one shrinker + * function in the shim layer for all slab caches. And we always + * attempt to shrink all caches when this generic shrinker is called. + */ + spin_lock(&kmem_cache_cb_lock); + + list_for_each_entry(kcc, &kmem_cache_cb_list, kcc_list) { + /* Under linux the desired number and gfp type of objects + * is passed to the reclaiming function as a sugested reclaim + * target. I do not pass these args on because reclaim + * policy is entirely up to the owner under solaris. We only + * pass on the pre-registered private data. + */ + if (kcc->kcc_reclaim) + kcc->kcc_reclaim(kcc->kcc_private); + + total += 1; + } + + /* Under linux we should return the remaining number of entires in + * the cache. Unfortunately, I don't see an easy way to safely + * emulate this behavior so I'm returning one entry per cache which + * was registered with the generic shrinker. This should fake out + * the linux VM when it attempts to shrink caches. + */ + spin_unlock(&kmem_cache_cb_lock); + return total; +} + +/* Ensure the __kmem_cache_create/__kmem_cache_destroy macros are + * removed here to prevent a recursive substitution, we want to call + * the native linux version. + */ +#undef kmem_cache_create +#undef kmem_cache_destroy + +kmem_cache_t * +__kmem_cache_create(char *name, size_t size, size_t align, + int (*constructor)(void *, void *, int), + void (*destructor)(void *, void *), + void (*reclaim)(void *), + void *priv, void *vmp, int flags) +{ + kmem_cache_t *cache; + kmem_cache_cb_t *kcc; + int shrinker_flag = 0; + + /* FIXME: - Option currently unsupported by shim layer */ + BUG_ON(vmp); + + cache = kmem_cache_create(name, size, align, flags, + kmem_cache_generic_constructor, + kmem_cache_generic_destructor); + if (cache == NULL) + return NULL; + + /* Register shared shrinker function on initial cache create */ + spin_lock(&kmem_cache_cb_lock); + if (list_empty(&kmem_cache_cb_list)) { + kmem_cache_shrinker = set_shrinker(KMC_DEFAULT_SEEKS, + kmem_cache_generic_shrinker); + if (kmem_cache_shrinker == NULL) { + kmem_cache_destroy(cache); + spin_unlock(&kmem_cache_cb_lock); + return NULL; + } + + } + spin_unlock(&kmem_cache_cb_lock); + + kcc = kmem_cache_add_cache_cb(cache, constructor, destructor, + reclaim, priv, vmp); + if (kcc == NULL) { + if (shrinker_flag) /* New shrinker registered must be removed */ + remove_shrinker(kmem_cache_shrinker); + + kmem_cache_destroy(cache); + return NULL; + } + + return cache; +} + +/* Return codes discarded because Solaris implementation has void return */ +void +__kmem_cache_destroy(kmem_cache_t *cache) +{ + kmem_cache_cb_t *kcc; + + spin_lock(&kmem_cache_cb_lock); + kcc = kmem_cache_find_cache_cb(cache); + spin_unlock(&kmem_cache_cb_lock); + if (kcc == NULL) + return; + + kmem_cache_destroy(cache); + kmem_cache_remove_cache_cb(kcc); + + /* Unregister generic shrinker on removal of all caches */ + spin_lock(&kmem_cache_cb_lock); + if (list_empty(&kmem_cache_cb_list)) + remove_shrinker(kmem_cache_shrinker); + + spin_unlock(&kmem_cache_cb_lock); +} + +void +__kmem_reap(void) { + /* Since there's no easy hook in to linux to force all the registered + * shrinkers to run we just run the ones registered for this shim */ + kmem_cache_generic_shrinker(KMC_REAP_CHUNK, GFP_KERNEL); +} + diff --git a/src/spl/linux-rwlock.c b/src/spl/linux-rwlock.c new file mode 100644 index 000000000..e95ec1555 --- /dev/null +++ b/src/spl/linux-rwlock.c @@ -0,0 +1,41 @@ +#include <sys/linux-rwlock.h> + +int +rw_lock_held(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + +#ifdef CONFIG_RWSEM_GENERIC_SPINLOCK + if (rwlp->rw_sem.activity != 0) { +#else + if (rwlp->rw_sem.count != 0) { +#endif + return 1; + } + + return 0; +} + +int +rw_read_held(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + + if (rw_lock_held(rwlp) && rwlp->rw_owner == NULL) { + return 1; + } + + return 0; +} + +int +rw_write_held(krwlock_t *rwlp) +{ + BUG_ON(rwlp->rw_magic != RW_MAGIC); + + if (rwlp->rw_owner == current) { + return 1; + } + + return 0; +} diff --git a/src/spl/linux-taskq.c b/src/spl/linux-taskq.c new file mode 100644 index 000000000..0babd2395 --- /dev/null +++ b/src/spl/linux-taskq.c @@ -0,0 +1,78 @@ +#include <sys/linux-taskq.h> + +/* + * Task queue interface + * + * The taskq_work_wrapper functions are used to manage the work_structs + * which must be submitted to linux. The shim layer allocates a wrapper + * structure for all items which contains a pointer to itself as well as + * the real work to be performed. When the work item run the generic + * handle is called which calls the real work function and then using + * the self pointer frees the work_struct. + */ +typedef struct taskq_work_wrapper { + struct work_struct tww_work; + task_func_t tww_func; + void * tww_priv; +} taskq_work_wrapper_t; + +static void +taskq_work_handler(void *priv) +{ + taskq_work_wrapper_t *tww = priv; + + BUG_ON(tww == NULL); + BUG_ON(tww->tww_func == NULL); + + /* Call the real function and free the wrapper */ + tww->tww_func(tww->tww_priv); + kfree(tww); +} + +/* XXX - All flags currently ignored */ +taskqid_t +__taskq_dispatch(taskq_t *tq, task_func_t func, void *priv, uint_t flags) +{ + struct workqueue_struct *wq = tq; + taskq_work_wrapper_t *tww; + int rc; + + + BUG_ON(in_interrupt()); + BUG_ON(tq == NULL); + BUG_ON(func == NULL); + + tww = (taskq_work_wrapper_t *)kmalloc(sizeof(*tww), GFP_KERNEL); + if (!tww) + return (taskqid_t)0; + + INIT_WORK(&(tww->tww_work), taskq_work_handler, tww); + tww->tww_func = func; + tww->tww_priv = priv; + + rc = queue_work(wq, &(tww->tww_work)); + if (!rc) { + kfree(tww); + return (taskqid_t)0; + } + + return (taskqid_t)wq; +} + +/* XXX - Most args ignored until we decide if it's worth the effort + * to emulate the solaris notion of dynamic thread pools. For + * now we simply serialize everything through one thread which + * may come back to bite us as a performance issue. + * pri - Ignore priority + * min - Ignored until this is a dynamic thread pool + * max - Ignored until this is a dynamic thread pool + * flags - Ignored until this is a dynamic thread_pool + */ +taskq_t * +__taskq_create(const char *name, int nthreads, pri_t pri, + int minalloc, int maxalloc, uint_t flags) +{ + /* NOTE: Linux workqueue names are limited to 10 chars */ + + return create_singlethread_workqueue(name); +} diff --git a/src/spl/linux-thread.c b/src/spl/linux-thread.c new file mode 100644 index 000000000..ad036471a --- /dev/null +++ b/src/spl/linux-thread.c @@ -0,0 +1,113 @@ +#include <sys/linux-thread.h> + +/* + * Thread interfaces + */ +typedef struct thread_priv_s { + unsigned long tp_magic; /* Magic */ + void (*tp_func)(void *); /* Registered function */ + void *tp_args; /* Args to be passed to function */ + size_t tp_len; /* Len to be passed to function */ + int tp_state; /* State to start thread at */ + pri_t tp_pri; /* Priority to start threat at */ + volatile kthread_t *tp_task; /* Task pointer for new thread */ + spinlock_t tp_lock; /* Syncronization lock */ + wait_queue_head_t tp_waitq; /* Syncronization wait queue */ +} thread_priv_t; + +int +thread_generic_wrapper(void *arg) +{ + thread_priv_t *tp = (thread_priv_t *)arg; + void (*func)(void *); + void *args; + char name[16]; + + /* Use the truncated function name as thread name */ + snprintf(name, sizeof(name), "%s", "kthread"); + daemonize(name); + + spin_lock(&tp->tp_lock); + BUG_ON(tp->tp_magic != TP_MAGIC); + func = tp->tp_func; + args = tp->tp_args; + tp->tp_task = get_current(); + set_current_state(tp->tp_state); + set_user_nice((kthread_t *)tp->tp_task, PRIO_TO_NICE(tp->tp_pri)); + + spin_unlock(&tp->tp_lock); + wake_up(&tp->tp_waitq); + + /* DO NOT USE 'ARG' AFTER THIS POINT, EVER, EVER, EVER! + * Local variables are used here because after the calling thread + * has been woken up it will exit and this memory will no longer + * be safe to access since it was declared on the callers stack. */ + if (func) + func(args); + + return 0; +} + +void +__thread_exit(void) +{ + return; +} + +/* thread_create() may block forever if it cannot create a thread or + * allocate memory. This is preferable to returning a NULL which Solaris + * style callers likely never check for... since it can't fail. */ +kthread_t * +__thread_create(caddr_t stk, size_t stksize, void (*proc)(void *), + void *args, size_t len, proc_t *pp, int state, pri_t pri) +{ + thread_priv_t tp; + DEFINE_WAIT(wait); + kthread_t *task; + long pid; + + /* Option pp is simply ignored */ + /* Variable stack size unsupported */ + BUG_ON(stk != NULL); + BUG_ON(stk != 0); + + /* Variable tp is located on the stack and not the heap because I want + * to minimize any chance of a failure, since the Solaris code is designed + * such that this function cannot fail. This is a little dangerous since + * we're passing a stack address to a new thread but correct locking was + * added to ensure the callee can use the data safely until wake_up(). */ + tp.tp_magic = TP_MAGIC; + tp.tp_func = proc; + tp.tp_args = args; + tp.tp_len = len; + tp.tp_state = state; + tp.tp_pri = pri; + tp.tp_task = NULL; + spin_lock_init(&tp.tp_lock); + init_waitqueue_head(&tp.tp_waitq); + + spin_lock(&tp.tp_lock); + + /* Solaris says this must never fail so we try forever */ + while ((pid = kernel_thread(thread_generic_wrapper, (void *)&tp, 0)) < 0) + printk(KERN_ERR "linux-thread: Unable to create thread; " + "pid = %ld\n", pid); + + /* All signals are ignored due to sleeping TASK_UNINTERRUPTIBLE */ + for (;;) { + prepare_to_wait(&tp.tp_waitq, &wait, TASK_UNINTERRUPTIBLE); + if (tp.tp_task != NULL) + break; + + spin_unlock(&tp.tp_lock); + schedule(); + spin_lock(&tp.tp_lock); + } + + /* Verify the pid retunred matches the pid in the task struct */ + BUG_ON(pid != (tp.tp_task)->pid); + + spin_unlock(&tp.tp_lock); + + return (kthread_t *)tp.tp_task; +} diff --git a/src/splat/Makefile.in b/src/splat/Makefile.in new file mode 100644 index 000000000..758c4be0a --- /dev/null +++ b/src/splat/Makefile.in @@ -0,0 +1,57 @@ +# Makefile.in for splat kernel module + +MODULES := splat + +DISTFILES = Makefile.in \ + splat-kmem.c splat-random.c splat-taskq.c \ + splat-time.c splat-condvar.c splat-mutex.c \ + splat-rwlock.c splat-thread.c splat-ctl.c + +# Removed '-std=gnu99' does to compile issues with i386 SPIN_LOCK_UNLOCKED +# EXTRA_CFLAGS += -I$(src) +# EXTRA_CFLAGS += -Wall -Wno-unknown-pragmas -Wno-missing-braces \ +# -Wno-sign-compare -Wno-parentheses -Wno-uninitialized \ +# -Wno-implicit-function-declaration -Wno-unused -Wno-trigraphs \ +# -Wno-char-subscripts -Wno-switch + +# Solaris porting layer aggressive tests +obj-m := splat.o + +splat-objs += splat-ctl.o +splat-objs += splat-kmem.o +splat-objs += splat-taskq.o +splat-objs += splat-random.o +splat-objs += splat-mutex.o +splat-objs += splat-condvar.o +splat-objs += splat-thread.o +splat-objs += splat-rwlock.o +splat-objs += splat-time.o + +splatmodule := splat.ko +splatmoduledir := @kmoduledir@/kernel/lib/ + +all: all-spec + +install: all + mkdir -p $(DESTDIR)$(splatmoduledir) + $(INSTALL) -m 644 $(splatmodule) $(DESTDIR)$(splatmoduledir)/$(splatmodule) + -/sbin/depmod -a + +uninstall: + rm -f $(DESTDIR)$(splatmoduledir)/$(splatmodule) + -/sbin/depmod -a + +clean: + -rm -f $(splmodule) *.o .*.cmd *.mod.c *.ko *.s */*.o + +distclean: clean + rm -f Makefile + rm -rf .tmp_versions + +maintainer-clean: distclean + +distdir: $(DISTFILES) + cp -p $(DISTFILES) $(distdir) + +all-spec: + $(MAKE) -C @kernelsrc@ SUBDIRS=`pwd` @KERNELMAKE_PARAMS@ modules diff --git a/src/splat/splat-condvar.c b/src/splat/splat-condvar.c new file mode 100644 index 000000000..eaab2ac0a --- /dev/null +++ b/src/splat/splat-condvar.c @@ -0,0 +1,454 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_CONDVAR 0x0500 +#define KZT_CONDVAR_NAME "condvar" +#define KZT_CONDVAR_DESC "Kernel Condition Variable Tests" + +#define KZT_CONDVAR_TEST1_ID 0x0501 +#define KZT_CONDVAR_TEST1_NAME "signal1" +#define KZT_CONDVAR_TEST1_DESC "Wake a single thread, cv_wait()/cv_signal()" + +#define KZT_CONDVAR_TEST2_ID 0x0502 +#define KZT_CONDVAR_TEST2_NAME "broadcast1" +#define KZT_CONDVAR_TEST2_DESC "Wake all threads, cv_wait()/cv_broadcast()" + +#define KZT_CONDVAR_TEST3_ID 0x0503 +#define KZT_CONDVAR_TEST3_NAME "signal2" +#define KZT_CONDVAR_TEST3_DESC "Wake a single thread, cv_wait_timeout()/cv_signal()" + +#define KZT_CONDVAR_TEST4_ID 0x0504 +#define KZT_CONDVAR_TEST4_NAME "broadcast2" +#define KZT_CONDVAR_TEST4_DESC "Wake all threads, cv_wait_timeout()/cv_broadcast()" + +#define KZT_CONDVAR_TEST5_ID 0x0505 +#define KZT_CONDVAR_TEST5_NAME "timeout" +#define KZT_CONDVAR_TEST5_DESC "Timeout thread, cv_wait_timeout()" + +#define KZT_CONDVAR_TEST_MAGIC 0x115599DDUL +#define KZT_CONDVAR_TEST_NAME "condvar_test" +#define KZT_CONDVAR_TEST_COUNT 8 + +typedef struct condvar_priv { + unsigned long cv_magic; + struct file *cv_file; + kcondvar_t cv_condvar; + kmutex_t cv_mtx; +} condvar_priv_t; + +typedef struct condvar_thr { + int ct_id; + const char *ct_name; + condvar_priv_t *ct_cvp; + int ct_rc; +} condvar_thr_t; + +int +kzt_condvar_test12_thread(void *arg) +{ + condvar_thr_t *ct = (condvar_thr_t *)arg; + condvar_priv_t *cv = ct->ct_cvp; + char name[16]; + + ASSERT(cv->cv_magic == KZT_CONDVAR_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", KZT_CONDVAR_TEST_NAME, ct->ct_id); + daemonize(name); + + mutex_enter(&cv->cv_mtx); + kzt_vprint(cv->cv_file, ct->ct_name, + "%s thread sleeping with %d waiters\n", + name, atomic_read(&cv->cv_condvar.cv_waiters)); + cv_wait(&cv->cv_condvar, &cv->cv_mtx); + kzt_vprint(cv->cv_file, ct->ct_name, + "%s thread woken %d waiters remain\n", + name, atomic_read(&cv->cv_condvar.cv_waiters)); + mutex_exit(&cv->cv_mtx); + + return 0; +} + +static int +kzt_condvar_test1(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_CONDVAR_TEST_COUNT]; + condvar_thr_t ct[KZT_CONDVAR_TEST_COUNT]; + condvar_priv_t cv; + + cv.cv_magic = KZT_CONDVAR_TEST_MAGIC; + cv.cv_file = file; + mutex_init(&cv.cv_mtx, KZT_CONDVAR_TEST_NAME, MUTEX_DEFAULT, NULL); + cv_init(&cv.cv_condvar, KZT_CONDVAR_TEST_NAME, CV_DEFAULT, NULL); + + /* 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 < KZT_CONDVAR_TEST_COUNT; i++) { + ct[i].ct_cvp = &cv; + ct[i].ct_id = i; + ct[i].ct_name = KZT_CONDVAR_TEST1_NAME; + ct[i].ct_rc = 0; + + pids[i] = kernel_thread(kzt_condvar_test12_thread, &ct[i], 0); + if (pids[i] >= 0) + count++; + } + + /* Wait until all threads are waiting on the condition variable */ + while (atomic_read(&cv.cv_condvar.cv_waiters) != count) + schedule(); + + /* Wake a single thread at a time, wait until it exits */ + for (i = 1; i <= count; i++) { + cv_signal(&cv.cv_condvar); + + while (atomic_read(&cv.cv_condvar.cv_waiters) > (count - i)) + schedule(); + + /* Correct behavior 1 thread woken */ + if (atomic_read(&cv.cv_condvar.cv_waiters) == (count - i)) + continue; + + kzt_vprint(file, KZT_CONDVAR_TEST1_NAME, "Attempted to " + "wake %d thread but work %d threads woke\n", + 1, count - atomic_read(&cv.cv_condvar.cv_waiters)); + rc = -EINVAL; + break; + } + + if (!rc) + kzt_vprint(file, KZT_CONDVAR_TEST1_NAME, "Correctly woke " + "%d sleeping threads %d at a time\n", count, 1); + + /* Wait until that last nutex is dropped */ + while (mutex_owner(&cv.cv_mtx)) + schedule(); + + /* Wake everything for the failure case */ + cv_broadcast(&cv.cv_condvar); + cv_destroy(&cv.cv_condvar); + mutex_destroy(&cv.cv_mtx); + + return rc; +} + +static int +kzt_condvar_test2(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_CONDVAR_TEST_COUNT]; + condvar_thr_t ct[KZT_CONDVAR_TEST_COUNT]; + condvar_priv_t cv; + + cv.cv_magic = KZT_CONDVAR_TEST_MAGIC; + cv.cv_file = file; + mutex_init(&cv.cv_mtx, KZT_CONDVAR_TEST_NAME, MUTEX_DEFAULT, NULL); + cv_init(&cv.cv_condvar, KZT_CONDVAR_TEST_NAME, CV_DEFAULT, NULL); + + /* 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 < KZT_CONDVAR_TEST_COUNT; i++) { + ct[i].ct_cvp = &cv; + ct[i].ct_id = i; + ct[i].ct_name = KZT_CONDVAR_TEST2_NAME; + ct[i].ct_rc = 0; + + pids[i] = kernel_thread(kzt_condvar_test12_thread, &ct[i], 0); + if (pids[i] > 0) + count++; + } + + /* Wait until all threads are waiting on the condition variable */ + while (atomic_read(&cv.cv_condvar.cv_waiters) != count) + schedule(); + + /* Wake all threads waiting on the condition variable */ + cv_broadcast(&cv.cv_condvar); + + /* Wait until all threads have exited */ + while ((atomic_read(&cv.cv_condvar.cv_waiters) > 0) || mutex_owner(&cv.cv_mtx)) + schedule(); + + kzt_vprint(file, KZT_CONDVAR_TEST2_NAME, "Correctly woke all " + "%d sleeping threads at once\n", count); + + /* Wake everything for the failure case */ + cv_destroy(&cv.cv_condvar); + mutex_destroy(&cv.cv_mtx); + + return rc; +} + +int +kzt_condvar_test34_thread(void *arg) +{ + condvar_thr_t *ct = (condvar_thr_t *)arg; + condvar_priv_t *cv = ct->ct_cvp; + char name[16]; + clock_t rc; + + ASSERT(cv->cv_magic == KZT_CONDVAR_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", KZT_CONDVAR_TEST_NAME, ct->ct_id); + daemonize(name); + + mutex_enter(&cv->cv_mtx); + kzt_vprint(cv->cv_file, ct->ct_name, + "%s thread sleeping with %d waiters\n", + name, atomic_read(&cv->cv_condvar.cv_waiters)); + + /* Sleep no longer than 3 seconds, for this test we should + * actually never sleep that long without being woken up. */ + rc = cv_timedwait(&cv->cv_condvar, &cv->cv_mtx, lbolt + HZ * 3); + if (rc == -1) { + ct->ct_rc = -ETIMEDOUT; + kzt_vprint(cv->cv_file, ct->ct_name, "%s thread timed out, " + "should have been woken\n", name); + } else { + kzt_vprint(cv->cv_file, ct->ct_name, + "%s thread woken %d waiters remain\n", + name, atomic_read(&cv->cv_condvar.cv_waiters)); + } + + mutex_exit(&cv->cv_mtx); + + return 0; +} + +static int +kzt_condvar_test3(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_CONDVAR_TEST_COUNT]; + condvar_thr_t ct[KZT_CONDVAR_TEST_COUNT]; + condvar_priv_t cv; + + cv.cv_magic = KZT_CONDVAR_TEST_MAGIC; + cv.cv_file = file; + mutex_init(&cv.cv_mtx, KZT_CONDVAR_TEST_NAME, MUTEX_DEFAULT, NULL); + cv_init(&cv.cv_condvar, KZT_CONDVAR_TEST_NAME, CV_DEFAULT, NULL); + + /* 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 < KZT_CONDVAR_TEST_COUNT; i++) { + ct[i].ct_cvp = &cv; + ct[i].ct_id = i; + ct[i].ct_name = KZT_CONDVAR_TEST3_NAME; + ct[i].ct_rc = 0; + + pids[i] = kernel_thread(kzt_condvar_test34_thread, &ct[i], 0); + if (pids[i] >= 0) + count++; + } + + /* Wait until all threads are waiting on the condition variable */ + while (atomic_read(&cv.cv_condvar.cv_waiters) != count) + schedule(); + + /* Wake a single thread at a time, wait until it exits */ + for (i = 1; i <= count; i++) { + cv_signal(&cv.cv_condvar); + + while (atomic_read(&cv.cv_condvar.cv_waiters) > (count - i)) + schedule(); + + /* Correct behavior 1 thread woken */ + if (atomic_read(&cv.cv_condvar.cv_waiters) == (count - i)) + continue; + + kzt_vprint(file, KZT_CONDVAR_TEST3_NAME, "Attempted to " + "wake %d thread but work %d threads woke\n", + 1, count - atomic_read(&cv.cv_condvar.cv_waiters)); + rc = -EINVAL; + break; + } + + /* Validate no waiting thread timed out early */ + for (i = 0; i < count; i++) + if (ct[i].ct_rc) + rc = ct[i].ct_rc; + + if (!rc) + kzt_vprint(file, KZT_CONDVAR_TEST3_NAME, "Correctly woke " + "%d sleeping threads %d at a time\n", count, 1); + + /* Wait until that last nutex is dropped */ + while (mutex_owner(&cv.cv_mtx)) + schedule(); + + /* Wake everything for the failure case */ + cv_broadcast(&cv.cv_condvar); + cv_destroy(&cv.cv_condvar); + mutex_destroy(&cv.cv_mtx); + + return rc; +} + +static int +kzt_condvar_test4(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_CONDVAR_TEST_COUNT]; + condvar_thr_t ct[KZT_CONDVAR_TEST_COUNT]; + condvar_priv_t cv; + + cv.cv_magic = KZT_CONDVAR_TEST_MAGIC; + cv.cv_file = file; + mutex_init(&cv.cv_mtx, KZT_CONDVAR_TEST_NAME, MUTEX_DEFAULT, NULL); + cv_init(&cv.cv_condvar, KZT_CONDVAR_TEST_NAME, CV_DEFAULT, NULL); + + /* 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 < KZT_CONDVAR_TEST_COUNT; i++) { + ct[i].ct_cvp = &cv; + ct[i].ct_id = i; + ct[i].ct_name = KZT_CONDVAR_TEST3_NAME; + ct[i].ct_rc = 0; + + pids[i] = kernel_thread(kzt_condvar_test34_thread, &ct[i], 0); + if (pids[i] >= 0) + count++; + } + + /* Wait until all threads are waiting on the condition variable */ + while (atomic_read(&cv.cv_condvar.cv_waiters) != count) + schedule(); + + /* Wake a single thread at a time, wait until it exits */ + for (i = 1; i <= count; i++) { + cv_signal(&cv.cv_condvar); + + while (atomic_read(&cv.cv_condvar.cv_waiters) > (count - i)) + schedule(); + + /* Correct behavior 1 thread woken */ + if (atomic_read(&cv.cv_condvar.cv_waiters) == (count - i)) + continue; + + kzt_vprint(file, KZT_CONDVAR_TEST3_NAME, "Attempted to " + "wake %d thread but work %d threads woke\n", + 1, count - atomic_read(&cv.cv_condvar.cv_waiters)); + rc = -EINVAL; + break; + } + + /* Validate no waiting thread timed out early */ + for (i = 0; i < count; i++) + if (ct[i].ct_rc) + rc = ct[i].ct_rc; + + if (!rc) + kzt_vprint(file, KZT_CONDVAR_TEST3_NAME, "Correctly woke " + "%d sleeping threads %d at a time\n", count, 1); + + /* Wait until that last nutex is dropped */ + while (mutex_owner(&cv.cv_mtx)) + schedule(); + + /* Wake everything for the failure case */ + cv_broadcast(&cv.cv_condvar); + cv_destroy(&cv.cv_condvar); + mutex_destroy(&cv.cv_mtx); + + return rc; +} + +static int +kzt_condvar_test5(struct file *file, void *arg) +{ + kcondvar_t condvar; + kmutex_t mtx; + clock_t time_left, time_before, time_after, time_delta; + int64_t whole_delta; + int32_t remain_delta; + int rc = 0; + + mutex_init(&mtx, KZT_CONDVAR_TEST_NAME, MUTEX_DEFAULT, NULL); + cv_init(&condvar, KZT_CONDVAR_TEST_NAME, CV_DEFAULT, NULL); + + kzt_vprint(file, KZT_CONDVAR_TEST5_NAME, "Thread going to sleep for " + "%d second and expecting to be woken by timeout\n", 1); + + /* Allow a 1 second timeout, plenty long to validate correctness. */ + time_before = lbolt; + mutex_enter(&mtx); + time_left = cv_timedwait(&condvar, &mtx, lbolt + HZ); + mutex_exit(&mtx); + time_after = lbolt; + time_delta = time_after - time_before; /* XXX - Handle jiffie wrap */ + whole_delta = time_delta; + remain_delta = do_div(whole_delta, HZ); + + if (time_left == -1) { + if (time_delta >= HZ) { + kzt_vprint(file, KZT_CONDVAR_TEST5_NAME, + "Thread correctly timed out and was asleep " + "for %d.%d seconds (%d second min)\n", + (int)whole_delta, remain_delta, 1); + } else { + kzt_vprint(file, KZT_CONDVAR_TEST5_NAME, + "Thread correctly timed out but was only " + "asleep for %d.%d seconds (%d second " + "min)\n", (int)whole_delta, remain_delta, 1); + rc = -ETIMEDOUT; + } + } else { + kzt_vprint(file, KZT_CONDVAR_TEST5_NAME, + "Thread exited after only %d.%d seconds, it " + "did not hit the %d second timeout\n", + (int)whole_delta, remain_delta, 1); + rc = -ETIMEDOUT; + } + + cv_destroy(&condvar); + mutex_destroy(&mtx); + + return rc; +} + +kzt_subsystem_t * +kzt_condvar_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_CONDVAR_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_CONDVAR_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_CONDVAR; + + KZT_TEST_INIT(sub, KZT_CONDVAR_TEST1_NAME, KZT_CONDVAR_TEST1_DESC, + KZT_CONDVAR_TEST1_ID, kzt_condvar_test1); + KZT_TEST_INIT(sub, KZT_CONDVAR_TEST2_NAME, KZT_CONDVAR_TEST2_DESC, + KZT_CONDVAR_TEST2_ID, kzt_condvar_test2); + KZT_TEST_INIT(sub, KZT_CONDVAR_TEST3_NAME, KZT_CONDVAR_TEST3_DESC, + KZT_CONDVAR_TEST3_ID, kzt_condvar_test3); + KZT_TEST_INIT(sub, KZT_CONDVAR_TEST4_NAME, KZT_CONDVAR_TEST4_DESC, + KZT_CONDVAR_TEST4_ID, kzt_condvar_test4); + KZT_TEST_INIT(sub, KZT_CONDVAR_TEST5_NAME, KZT_CONDVAR_TEST5_DESC, + KZT_CONDVAR_TEST5_ID, kzt_condvar_test5); + + return sub; +} + +void +kzt_condvar_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_CONDVAR_TEST5_ID); + KZT_TEST_FINI(sub, KZT_CONDVAR_TEST4_ID); + KZT_TEST_FINI(sub, KZT_CONDVAR_TEST3_ID); + KZT_TEST_FINI(sub, KZT_CONDVAR_TEST2_ID); + KZT_TEST_FINI(sub, KZT_CONDVAR_TEST1_ID); + + kfree(sub); +} + +int +kzt_condvar_id(void) { + return KZT_SUBSYSTEM_CONDVAR; +} diff --git a/src/splat/splat-ctl.c b/src/splat/splat-ctl.c new file mode 100644 index 000000000..5292b0e60 --- /dev/null +++ b/src/splat/splat-ctl.c @@ -0,0 +1,684 @@ +/* + * My intent is the create a loadable kzt (kernel ZFS test) module + * which can be used as an access point to run in kernel ZFS regression + * tests. Why do we need this when we have ztest? Well ztest.c only + * excersises the ZFS code proper, it cannot be used to validate the + * linux kernel shim primatives. This also provides a nice hook for + * any other in kernel regression tests we wish to run such as direct + * in-kernel tests against the DMU. + * + * The basic design is the kzt module is that it is constructed of + * various kzt_* source files each of which contains regression tests. + * For example the kzt_linux_kmem.c file contains tests for validating + * kmem correctness. When the kzt module is loaded kzt_*_init() + * will be called for each subsystems tests, similarly kzt_*_fini() is + * called when the kzt module is removed. Each test can then be + * run by making an ioctl() call from a userspace control application + * to pick the subsystem and test which should be run. + * + * Author: Brian Behlendorf + */ + +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#include <linux/version.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/device.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) +#include <linux/devfs_fs_kernel.h> +#endif + +#include <linux/cdev.h> + + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) +static struct class_simple *kzt_class; +#else +static struct class *kzt_class; +#endif +static struct list_head kzt_module_list; +static spinlock_t kzt_module_lock; + +static int +kzt_open(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + kzt_info_t *info; + + if (minor >= KZT_MINORS) + return -ENXIO; + + info = (kzt_info_t *)kmalloc(sizeof(*info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + + spin_lock_init(&info->info_lock); + info->info_size = KZT_INFO_BUFFER_SIZE; + info->info_buffer = (char *)vmalloc(KZT_INFO_BUFFER_SIZE); + if (info->info_buffer == NULL) { + kfree(info); + return -ENOMEM; + } + + info->info_head = info->info_buffer; + file->private_data = (void *)info; + + kzt_print(file, "Kernel ZFS Tests %s\n", KZT_VERSION); + + return 0; +} + +static int +kzt_release(struct inode *inode, struct file *file) +{ + unsigned int minor = iminor(inode); + kzt_info_t *info = (kzt_info_t *)file->private_data; + + if (minor >= KZT_MINORS) + return -ENXIO; + + ASSERT(info); + ASSERT(info->info_buffer); + + vfree(info->info_buffer); + kfree(info); + + return 0; +} + +static int +kzt_buffer_clear(struct file *file, kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_info_t *info = (kzt_info_t *)file->private_data; + + ASSERT(info); + ASSERT(info->info_buffer); + + spin_lock(&info->info_lock); + memset(info->info_buffer, 0, info->info_size); + info->info_head = info->info_buffer; + spin_unlock(&info->info_lock); + + return 0; +} + +static int +kzt_buffer_size(struct file *file, kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_info_t *info = (kzt_info_t *)file->private_data; + char *buf; + int min, size, rc = 0; + + ASSERT(info); + ASSERT(info->info_buffer); + + spin_lock(&info->info_lock); + if (kcfg->cfg_arg1 > 0) { + + size = kcfg->cfg_arg1; + buf = (char *)vmalloc(size); + if (buf == NULL) { + rc = -ENOMEM; + goto out; + } + + /* Zero fill and truncate contents when coping buffer */ + min = ((size < info->info_size) ? size : info->info_size); + memset(buf, 0, size); + memcpy(buf, info->info_buffer, min); + vfree(info->info_buffer); + info->info_size = size; + info->info_buffer = buf; + info->info_head = info->info_buffer; + } + + kcfg->cfg_rc1 = info->info_size; + + if (copy_to_user((struct kzt_cfg_t __user *)arg, kcfg, sizeof(*kcfg))) + rc = -EFAULT; +out: + spin_unlock(&info->info_lock); + + return rc; +} + + +static kzt_subsystem_t * +kzt_subsystem_find(int id) { + kzt_subsystem_t *sub; + + spin_lock(&kzt_module_lock); + list_for_each_entry(sub, &kzt_module_list, subsystem_list) { + if (id == sub->desc.id) { + spin_unlock(&kzt_module_lock); + return sub; + } + } + spin_unlock(&kzt_module_lock); + + return NULL; +} + +static int +kzt_subsystem_count(kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_subsystem_t *sub; + int i = 0; + + spin_lock(&kzt_module_lock); + list_for_each_entry(sub, &kzt_module_list, subsystem_list) + i++; + + spin_unlock(&kzt_module_lock); + kcfg->cfg_rc1 = i; + + if (copy_to_user((struct kzt_cfg_t __user *)arg, kcfg, sizeof(*kcfg))) + return -EFAULT; + + return 0; +} + +static int +kzt_subsystem_list(kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_subsystem_t *sub; + kzt_cfg_t *tmp; + int size, i = 0; + + /* Structure will be sized large enough for N subsystem entries + * which is passed in by the caller. On exit the number of + * entries filled in with valid subsystems will be stored in + * cfg_rc1. If the caller does not provide enough entries + * for all subsystems we will truncate the list to avoid overrun. + */ + size = sizeof(*tmp) + kcfg->cfg_data.kzt_subsystems.size * + sizeof(kzt_user_t); + tmp = kmalloc(size, GFP_KERNEL); + if (tmp == NULL) + return -ENOMEM; + + /* Local 'tmp' is used as the structure copied back to user space */ + memset(tmp, 0, size); + memcpy(tmp, kcfg, sizeof(*kcfg)); + + spin_lock(&kzt_module_lock); + list_for_each_entry(sub, &kzt_module_list, subsystem_list) { + strncpy(tmp->cfg_data.kzt_subsystems.descs[i].name, + sub->desc.name, KZT_NAME_SIZE); + strncpy(tmp->cfg_data.kzt_subsystems.descs[i].desc, + sub->desc.desc, KZT_DESC_SIZE); + tmp->cfg_data.kzt_subsystems.descs[i].id = sub->desc.id; + + /* Truncate list if we are about to overrun alloc'ed memory */ + if ((i++) == kcfg->cfg_data.kzt_subsystems.size) + break; + } + spin_unlock(&kzt_module_lock); + tmp->cfg_rc1 = i; + + if (copy_to_user((struct kzt_cfg_t __user *)arg, tmp, size)) { + kfree(tmp); + return -EFAULT; + } + + kfree(tmp); + return 0; +} + +static int +kzt_test_count(kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_subsystem_t *sub; + kzt_test_t *test; + int rc, i = 0; + + /* Subsystem ID passed as arg1 */ + sub = kzt_subsystem_find(kcfg->cfg_arg1); + if (sub == NULL) + return -EINVAL; + + spin_lock(&(sub->test_lock)); + list_for_each_entry(test, &(sub->test_list), test_list) + i++; + + spin_unlock(&(sub->test_lock)); + kcfg->cfg_rc1 = i; + + if (copy_to_user((struct kzt_cfg_t __user *)arg, kcfg, sizeof(*kcfg))) + return -EFAULT; + + return 0; +} + +static int +kzt_test_list(kzt_cfg_t *kcfg, unsigned long arg) +{ + kzt_subsystem_t *sub; + kzt_test_t *test; + kzt_cfg_t *tmp; + int size, rc, i = 0; + + /* Subsystem ID passed as arg1 */ + sub = kzt_subsystem_find(kcfg->cfg_arg1); + if (sub == NULL) + return -EINVAL; + + /* Structure will be sized large enough for N test entries + * which is passed in by the caller. On exit the number of + * entries filled in with valid tests will be stored in + * cfg_rc1. If the caller does not provide enough entries + * for all tests we will truncate the list to avoid overrun. + */ + size = sizeof(*tmp)+kcfg->cfg_data.kzt_tests.size*sizeof(kzt_user_t); + tmp = kmalloc(size, GFP_KERNEL); + if (tmp == NULL) + return -ENOMEM; + + /* Local 'tmp' is used as the structure copied back to user space */ + memset(tmp, 0, size); + memcpy(tmp, kcfg, sizeof(*kcfg)); + + spin_lock(&(sub->test_lock)); + list_for_each_entry(test, &(sub->test_list), test_list) { + strncpy(tmp->cfg_data.kzt_tests.descs[i].name, + test->desc.name, KZT_NAME_SIZE); + strncpy(tmp->cfg_data.kzt_tests.descs[i].desc, + test->desc.desc, KZT_DESC_SIZE); + tmp->cfg_data.kzt_tests.descs[i].id = test->desc.id; + + /* Truncate list if we are about to overrun alloc'ed memory */ + if ((i++) == kcfg->cfg_data.kzt_tests.size) + break; + } + spin_unlock(&(sub->test_lock)); + tmp->cfg_rc1 = i; + + if (copy_to_user((struct kzt_cfg_t __user *)arg, tmp, size)) { + kfree(tmp); + return -EFAULT; + } + + kfree(tmp); + return 0; +} + +static int +kzt_validate(struct file *file, kzt_subsystem_t *sub, int cmd, void *arg) +{ + kzt_test_t *test; + int rc = 0; + + spin_lock(&(sub->test_lock)); + list_for_each_entry(test, &(sub->test_list), test_list) { + if (test->desc.id == cmd) { + spin_unlock(&(sub->test_lock)); + return test->test(file, arg); + } + } + spin_unlock(&(sub->test_lock)); + + return -EINVAL; +} + +static int +kzt_ioctl_cfg(struct file *file, unsigned long arg) +{ + kzt_cfg_t kcfg; + int rc = 0; + + if (copy_from_user(&kcfg, (kzt_cfg_t *)arg, sizeof(kcfg))) + return -EFAULT; + + if (kcfg.cfg_magic != KZT_CFG_MAGIC) { + kzt_print(file, "Bad config magic 0x%x != 0x%x\n", + kcfg.cfg_magic, KZT_CFG_MAGIC); + return -EINVAL; + } + + switch (kcfg.cfg_cmd) { + case KZT_CFG_BUFFER_CLEAR: + /* cfg_arg1 - Unused + * cfg_rc1 - Unused + */ + rc = kzt_buffer_clear(file, &kcfg, arg); + break; + case KZT_CFG_BUFFER_SIZE: + /* cfg_arg1 - 0 - query size; >0 resize + * cfg_rc1 - Set to current buffer size + */ + rc = kzt_buffer_size(file, &kcfg, arg); + break; + case KZT_CFG_SUBSYSTEM_COUNT: + /* cfg_arg1 - Unused + * cfg_rc1 - Set to number of subsystems + */ + rc = kzt_subsystem_count(&kcfg, arg); + break; + case KZT_CFG_SUBSYSTEM_LIST: + /* cfg_arg1 - Unused + * cfg_rc1 - Set to number of subsystems + * cfg_data.kzt_subsystems - Populated with subsystems + */ + rc = kzt_subsystem_list(&kcfg, arg); + break; + case KZT_CFG_TEST_COUNT: + /* cfg_arg1 - Set to a target subsystem + * cfg_rc1 - Set to number of tests + */ + rc = kzt_test_count(&kcfg, arg); + break; + case KZT_CFG_TEST_LIST: + /* cfg_arg1 - Set to a target subsystem + * cfg_rc1 - Set to number of tests + * cfg_data.kzt_subsystems - Populated with tests + */ + rc = kzt_test_list(&kcfg, arg); + break; + default: + kzt_print(file, "Bad config command %d\n", kcfg.cfg_cmd); + rc = -EINVAL; + break; + } + + return rc; +} + +static int +kzt_ioctl_cmd(struct file *file, unsigned long arg) +{ + kzt_subsystem_t *sub; + kzt_cmd_t kcmd; + int rc = -EINVAL; + void *data = NULL; + + if (copy_from_user(&kcmd, (kzt_cfg_t *)arg, sizeof(kcmd))) + return -EFAULT; + + if (kcmd.cmd_magic != KZT_CMD_MAGIC) { + kzt_print(file, "Bad command magic 0x%x != 0x%x\n", + kcmd.cmd_magic, KZT_CFG_MAGIC); + return -EINVAL; + } + + /* Allocate memory for any opaque data the caller needed to pass on */ + if (kcmd.cmd_data_size > 0) { + data = (void *)kmalloc(kcmd.cmd_data_size, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (copy_from_user(data, (void *)(arg + offsetof(kzt_cmd_t, + cmd_data_str)), kcmd.cmd_data_size)) { + kfree(data); + return -EFAULT; + } + } + + sub = kzt_subsystem_find(kcmd.cmd_subsystem); + if (sub != NULL) + rc = kzt_validate(file, sub, kcmd.cmd_test, data); + else + rc = -EINVAL; + + if (data != NULL) + kfree(data); + + return rc; +} + +static int +kzt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int minor, rc = 0; + + /* Ignore tty ioctls */ + if ((cmd & 0xffffff00) == ((int)'T') << 8) + return -ENOTTY; + + if (minor >= KZT_MINORS) + return -ENXIO; + + switch (cmd) { + case KZT_CFG: + rc = kzt_ioctl_cfg(file, arg); + break; + case KZT_CMD: + rc = kzt_ioctl_cmd(file, arg); + break; + default: + kzt_print(file, "Bad ioctl command %d\n", cmd); + rc = -EINVAL; + break; + } + + return rc; +} + +/* I'm not sure why you would want to write in to this buffer from + * user space since its principle use is to pass test status info + * back to the user space, but I don't see any reason to prevent it. + */ +static ssize_t kzt_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = iminor(file->f_dentry->d_inode); + kzt_info_t *info = (kzt_info_t *)file->private_data; + int rc = 0; + + if (minor >= KZT_MINORS) + return -ENXIO; + + ASSERT(info); + ASSERT(info->info_buffer); + + spin_lock(&info->info_lock); + + /* Write beyond EOF */ + if (*ppos >= info->info_size) { + rc = -EFBIG; + goto out; + } + + /* Resize count if beyond EOF */ + if (*ppos + count > info->info_size) + count = info->info_size - *ppos; + + if (copy_from_user(info->info_buffer, buf, count)) { + rc = -EFAULT; + goto out; + } + + *ppos += count; + rc = count; +out: + spin_unlock(&info->info_lock); + return rc; +} + +static ssize_t kzt_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = iminor(file->f_dentry->d_inode); + kzt_info_t *info = (kzt_info_t *)file->private_data; + int rc = 0; + + if (minor >= KZT_MINORS) + return -ENXIO; + + ASSERT(info); + ASSERT(info->info_buffer); + + spin_lock(&info->info_lock); + + /* Read beyond EOF */ + if (*ppos >= info->info_size) + goto out; + + /* Resize count if beyond EOF */ + if (*ppos + count > info->info_size) + count = info->info_size - *ppos; + + if (copy_to_user(buf, info->info_buffer + *ppos, count)) { + rc = -EFAULT; + goto out; + } + + *ppos += count; + rc = count; +out: + spin_unlock(&info->info_lock); + return rc; +} + +static loff_t kzt_seek(struct file *file, loff_t offset, int origin) +{ + unsigned int minor = iminor(file->f_dentry->d_inode); + kzt_info_t *info = (kzt_info_t *)file->private_data; + int rc = -EINVAL; + + if (minor >= KZT_MINORS) + return -ENXIO; + + ASSERT(info); + ASSERT(info->info_buffer); + + spin_lock(&info->info_lock); + + switch (origin) { + case 0: /* SEEK_SET - No-op just do it */ + break; + case 1: /* SEEK_CUR - Seek from current */ + offset = file->f_pos + offset; + break; + case 2: /* SEEK_END - Seek from end */ + offset = info->info_size + offset; + break; + } + + if (offset >= 0) { + file->f_pos = offset; + file->f_version = 0; + rc = offset; + } + + spin_unlock(&info->info_lock); + + return rc; +} + +static struct file_operations kzt_fops = { + .owner = THIS_MODULE, + .open = kzt_open, + .release = kzt_release, + .ioctl = kzt_ioctl, + .read = kzt_read, + .write = kzt_write, + .llseek = kzt_seek, +}; + +static struct cdev kzt_cdev = { + .owner = THIS_MODULE, + .kobj = { .name = "kztctl", }, +}; + +static int __init +kzt_init(void) +{ + dev_t dev; + int i, rc; + + spin_lock_init(&kzt_module_lock); + INIT_LIST_HEAD(&kzt_module_list); + + KZT_SUBSYSTEM_INIT(kmem); + KZT_SUBSYSTEM_INIT(taskq); + KZT_SUBSYSTEM_INIT(krng); + KZT_SUBSYSTEM_INIT(mutex); + KZT_SUBSYSTEM_INIT(condvar); + KZT_SUBSYSTEM_INIT(thread); + KZT_SUBSYSTEM_INIT(rwlock); + KZT_SUBSYSTEM_INIT(time); + + dev = MKDEV(KZT_MAJOR, 0); + if (rc = register_chrdev_region(dev, KZT_MINORS, "kztctl")) + goto error; + + /* Support for registering a character driver */ + cdev_init(&kzt_cdev, &kzt_fops); + if ((rc = cdev_add(&kzt_cdev, dev, KZT_MINORS))) { + printk(KERN_ERR "kzt: Error adding cdev, %d\n", rc); + kobject_put(&kzt_cdev.kobj); + unregister_chrdev_region(dev, KZT_MINORS); + goto error; + } + + /* Support for udev make driver info available in sysfs */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) + kzt_class = class_simple_create(THIS_MODULE, "kzt"); +#else + kzt_class = class_create(THIS_MODULE, "kzt"); +#endif + if (IS_ERR(kzt_class)) { + rc = PTR_ERR(kzt_class); + printk(KERN_ERR "kzt: Error creating kzt class, %d\n", rc); + cdev_del(&kzt_cdev); + unregister_chrdev_region(dev, KZT_MINORS); + goto error; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) + class_simple_device_add(kzt_class, MKDEV(KZT_MAJOR, 0), + NULL, "kztctl"); +#else + class_device_create(kzt_class, NULL, MKDEV(KZT_MAJOR, 0), + NULL, "kztctl"); +#endif + + printk(KERN_INFO "kzt: Kernel ZFS Tests %s Loaded\n", KZT_VERSION); + return 0; +error: + printk(KERN_ERR "kzt: Error registering kzt device, %d\n", rc); + return rc; +} + +static void +kzt_fini(void) +{ + dev_t dev = MKDEV(KZT_MAJOR, 0); + int i; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18) + class_simple_device_remove(dev); + class_simple_destroy(kzt_class); + devfs_remove("kzt/kztctl"); + devfs_remove("kzt"); +#else + class_device_destroy(kzt_class, dev); + class_destroy(kzt_class); +#endif + cdev_del(&kzt_cdev); + unregister_chrdev_region(dev, KZT_MINORS); + + KZT_SUBSYSTEM_FINI(time); + KZT_SUBSYSTEM_FINI(rwlock); + KZT_SUBSYSTEM_FINI(thread); + KZT_SUBSYSTEM_FINI(condvar); + KZT_SUBSYSTEM_FINI(mutex); + KZT_SUBSYSTEM_FINI(krng); + KZT_SUBSYSTEM_FINI(taskq); + KZT_SUBSYSTEM_FINI(kmem); + + ASSERT(list_empty(&kzt_module_list)); + printk(KERN_INFO "kzt: Kernel ZFS Tests %s Unloaded\n", KZT_VERSION); +} + +module_init(kzt_init); +module_exit(kzt_fini); + +MODULE_AUTHOR("Lawrence Livermore National Labs"); +MODULE_DESCRIPTION("Kernel ZFS Test"); +MODULE_LICENSE("GPL"); + diff --git a/src/splat/splat-kmem.c b/src/splat/splat-kmem.c new file mode 100644 index 000000000..fb40819b5 --- /dev/null +++ b/src/splat/splat-kmem.c @@ -0,0 +1,365 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_KMEM 0x0100 +#define KZT_KMEM_NAME "kmem" +#define KZT_KMEM_DESC "Kernel Malloc/Slab Tests" + +#define KZT_KMEM_TEST1_ID 0x0101 +#define KZT_KMEM_TEST1_NAME "kmem_alloc" +#define KZT_KMEM_TEST1_DESC "Memory allocation test (kmem_alloc)" + +#define KZT_KMEM_TEST2_ID 0x0102 +#define KZT_KMEM_TEST2_NAME "kmem_zalloc" +#define KZT_KMEM_TEST2_DESC "Memory allocation test (kmem_zalloc)" + +#define KZT_KMEM_TEST3_ID 0x0103 +#define KZT_KMEM_TEST3_NAME "slab_alloc" +#define KZT_KMEM_TEST3_DESC "Slab constructor/destructor test" + +#define KZT_KMEM_TEST4_ID 0x0104 +#define KZT_KMEM_TEST4_NAME "slab_reap" +#define KZT_KMEM_TEST4_DESC "Slab reaping test" + +#define KZT_KMEM_ALLOC_COUNT 10 +/* XXX - This test may fail under tight memory conditions */ +static int +kzt_kmem_test1(struct file *file, void *arg) +{ + void *ptr[KZT_KMEM_ALLOC_COUNT]; + int size = PAGE_SIZE; + int i, count, rc = 0; + + while ((!rc) && (size < (PAGE_SIZE * 16))) { + count = 0; + + for (i = 0; i < KZT_KMEM_ALLOC_COUNT; i++) { + ptr[i] = kmem_alloc(size, KM_SLEEP); + if (ptr[i]) + count++; + } + + for (i = 0; i < KZT_KMEM_ALLOC_COUNT; i++) + if (ptr[i]) + kmem_free(ptr[i], size); + + kzt_vprint(file, KZT_KMEM_TEST1_NAME, + "%d byte allocations, %d/%d successful\n", + size, count, KZT_KMEM_ALLOC_COUNT); + if (count != KZT_KMEM_ALLOC_COUNT) + rc = -ENOMEM; + + size *= 2; + } + + return rc; +} + +static int +kzt_kmem_test2(struct file *file, void *arg) +{ + void *ptr[KZT_KMEM_ALLOC_COUNT]; + int size = PAGE_SIZE; + int i, j, count, rc = 0; + + while ((!rc) && (size < (PAGE_SIZE * 16))) { + count = 0; + + for (i = 0; i < KZT_KMEM_ALLOC_COUNT; i++) { + ptr[i] = kmem_zalloc(size, KM_SLEEP); + if (ptr[i]) + count++; + } + + /* Ensure buffer has been zero filled */ + for (i = 0; i < KZT_KMEM_ALLOC_COUNT; i++) { + for (j = 0; j < size; j++) { + if (((char *)ptr[i])[j] != '\0') { + kzt_vprint(file, KZT_KMEM_TEST2_NAME, + "%d-byte allocation was " + "not zeroed\n", size); + rc = -EFAULT; + } + } + } + + for (i = 0; i < KZT_KMEM_ALLOC_COUNT; i++) + if (ptr[i]) + kmem_free(ptr[i], size); + + kzt_vprint(file, KZT_KMEM_TEST2_NAME, + "%d byte allocations, %d/%d successful\n", + size, count, KZT_KMEM_ALLOC_COUNT); + if (count != KZT_KMEM_ALLOC_COUNT) + rc = -ENOMEM; + + size *= 2; + } + + return rc; +} + +#define KZT_KMEM_TEST_MAGIC 0x004488CCUL +#define KZT_KMEM_CACHE_NAME "kmem_test" +#define KZT_KMEM_CACHE_SIZE 256 +#define KZT_KMEM_OBJ_COUNT 128 +#define KZT_KMEM_OBJ_RECLAIM 64 + +typedef struct kmem_cache_data { + char kcd_buf[KZT_KMEM_CACHE_SIZE]; + unsigned long kcd_magic; + int kcd_flag; +} kmem_cache_data_t; + +typedef struct kmem_cache_priv { + unsigned long kcp_magic; + struct file *kcp_file; + kmem_cache_t *kcp_cache; + kmem_cache_data_t *kcp_kcd[KZT_KMEM_OBJ_COUNT]; + int kcp_count; + int kcp_rc; +} kmem_cache_priv_t; + +static int +kzt_kmem_test34_constructor(void *ptr, void *priv, int flags) +{ + kmem_cache_data_t *kcd = (kmem_cache_data_t *)ptr; + kmem_cache_priv_t *kcp = (kmem_cache_priv_t *)priv; + + if (kcd) { + memset(kcd->kcd_buf, 0xaa, KZT_KMEM_CACHE_SIZE); + kcd->kcd_flag = 1; + + if (kcp) { + kcd->kcd_magic = kcp->kcp_magic; + kcp->kcp_count++; + } + } + + return 0; +} + +static void +kzt_kmem_test34_destructor(void *ptr, void *priv) +{ + kmem_cache_data_t *kcd = (kmem_cache_data_t *)ptr; + kmem_cache_priv_t *kcp = (kmem_cache_priv_t *)priv; + + if (kcd) { + memset(kcd->kcd_buf, 0xbb, KZT_KMEM_CACHE_SIZE); + kcd->kcd_flag = 0; + + if (kcp) + kcp->kcp_count--; + } + + return; +} + +static int +kzt_kmem_test3(struct file *file, void *arg) +{ + kmem_cache_t *cache = NULL; + kmem_cache_data_t *kcd = NULL; + kmem_cache_priv_t kcp; + int rc = 0, max; + + kcp.kcp_magic = KZT_KMEM_TEST_MAGIC; + kcp.kcp_file = file; + kcp.kcp_count = 0; + kcp.kcp_rc = 0; + + cache = kmem_cache_create(KZT_KMEM_CACHE_NAME, sizeof(*kcd), 0, + kzt_kmem_test34_constructor, + kzt_kmem_test34_destructor, + NULL, &kcp, NULL, 0); + if (!cache) { + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "Unable to create '%s'\n", KZT_KMEM_CACHE_NAME); + return -ENOMEM; + } + + kcd = kmem_cache_alloc(cache, 0); + if (!kcd) { + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "Unable to allocate from '%s'\n", + KZT_KMEM_CACHE_NAME); + rc = -EINVAL; + goto out_free; + } + + if (!kcd->kcd_flag) { + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "Failed to run contructor for '%s'\n", + KZT_KMEM_CACHE_NAME); + rc = -EINVAL; + goto out_free; + } + + if (kcd->kcd_magic != kcp.kcp_magic) { + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "Failed to pass private data to constructor " + "for '%s'\n", KZT_KMEM_CACHE_NAME); + rc = -EINVAL; + goto out_free; + } + + max = kcp.kcp_count; + + /* Destructor's run lazily so it hard to check correctness here. + * We assume if it doesn't crash the free worked properly */ + kmem_cache_free(cache, kcd); + + /* Destroy the entire cache which will force destructors to + * run and we can verify one was called for every object */ + kmem_cache_destroy(cache); + if (kcp.kcp_count) { + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "Failed to run destructor on all slab objects " + "for '%s'\n", KZT_KMEM_CACHE_NAME); + rc = -EINVAL; + } + + kzt_vprint(file, KZT_KMEM_TEST3_NAME, + "%d allocated/destroyed objects for '%s'\n", + max, KZT_KMEM_CACHE_NAME); + + return rc; + +out_free: + if (kcd) + kmem_cache_free(cache, kcd); +out_destroy: + kmem_cache_destroy(cache); + return rc; +} + +static void +kzt_kmem_test4_reclaim(void *priv) +{ + kmem_cache_priv_t *kcp = (kmem_cache_priv_t *)priv; + int i; + + kzt_vprint(kcp->kcp_file, KZT_KMEM_TEST4_NAME, + "Reaping %d objects from '%s'\n", + KZT_KMEM_OBJ_RECLAIM, KZT_KMEM_CACHE_NAME); + for (i = 0; i < KZT_KMEM_OBJ_RECLAIM; i++) { + if (kcp->kcp_kcd[i]) { + kmem_cache_free(kcp->kcp_cache, kcp->kcp_kcd[i]); + kcp->kcp_kcd[i] = NULL; + } + } + + return; +} + +static int +kzt_kmem_test4(struct file *file, void *arg) +{ + kmem_cache_t *cache; + kmem_cache_priv_t kcp; + int i, rc = 0, max, reclaim_percent, target_percent; + + kcp.kcp_magic = KZT_KMEM_TEST_MAGIC; + kcp.kcp_file = file; + kcp.kcp_count = 0; + kcp.kcp_rc = 0; + + cache = kmem_cache_create(KZT_KMEM_CACHE_NAME, + sizeof(kmem_cache_data_t), 0, + kzt_kmem_test34_constructor, + kzt_kmem_test34_destructor, + kzt_kmem_test4_reclaim, &kcp, NULL, 0); + if (!cache) { + kzt_vprint(file, KZT_KMEM_TEST4_NAME, + "Unable to create '%s'\n", KZT_KMEM_CACHE_NAME); + return -ENOMEM; + } + + kcp.kcp_cache = cache; + + for (i = 0; i < KZT_KMEM_OBJ_COUNT; i++) { + /* All allocations need not succeed */ + kcp.kcp_kcd[i] = kmem_cache_alloc(cache, 0); + if (!kcp.kcp_kcd[i]) { + kzt_vprint(file, KZT_KMEM_TEST4_NAME, + "Unable to allocate from '%s'\n", + KZT_KMEM_CACHE_NAME); + } + } + + max = kcp.kcp_count; + + /* Force shrinker to run */ + kmem_reap(); + + /* Reclaim reclaimed objects, this ensure the destructors are run */ + kmem_cache_reap_now(cache); + + reclaim_percent = ((kcp.kcp_count * 100) / max); + target_percent = (((KZT_KMEM_OBJ_COUNT - KZT_KMEM_OBJ_RECLAIM) * 100) / + KZT_KMEM_OBJ_COUNT); + kzt_vprint(file, KZT_KMEM_TEST4_NAME, + "%d%% (%d/%d) of previous size, target of " + "%d%%-%d%% for '%s'\n", reclaim_percent, kcp.kcp_count, + max, target_percent - 10, target_percent + 10, + KZT_KMEM_CACHE_NAME); + if ((reclaim_percent < target_percent - 10) || + (reclaim_percent > target_percent + 10)) + rc = -EINVAL; + + /* Cleanup our mess */ + for (i = 0; i < KZT_KMEM_OBJ_COUNT; i++) + if (kcp.kcp_kcd[i]) + kmem_cache_free(cache, kcp.kcp_kcd[i]); + + kmem_cache_destroy(cache); + + return rc; +} + +kzt_subsystem_t * +kzt_kmem_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_KMEM_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_KMEM_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_KMEM; + + KZT_TEST_INIT(sub, KZT_KMEM_TEST1_NAME, KZT_KMEM_TEST1_DESC, + KZT_KMEM_TEST1_ID, kzt_kmem_test1); + KZT_TEST_INIT(sub, KZT_KMEM_TEST2_NAME, KZT_KMEM_TEST2_DESC, + KZT_KMEM_TEST2_ID, kzt_kmem_test2); + KZT_TEST_INIT(sub, KZT_KMEM_TEST3_NAME, KZT_KMEM_TEST3_DESC, + KZT_KMEM_TEST3_ID, kzt_kmem_test3); + KZT_TEST_INIT(sub, KZT_KMEM_TEST4_NAME, KZT_KMEM_TEST4_DESC, + KZT_KMEM_TEST4_ID, kzt_kmem_test4); + + return sub; +} + +void +kzt_kmem_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_KMEM_TEST4_ID); + KZT_TEST_FINI(sub, KZT_KMEM_TEST3_ID); + KZT_TEST_FINI(sub, KZT_KMEM_TEST2_ID); + KZT_TEST_FINI(sub, KZT_KMEM_TEST1_ID); + + kfree(sub); +} + +int +kzt_kmem_id(void) { + return KZT_SUBSYSTEM_KMEM; +} diff --git a/src/splat/splat-mutex.c b/src/splat/splat-mutex.c new file mode 100644 index 000000000..254a40de2 --- /dev/null +++ b/src/splat/splat-mutex.c @@ -0,0 +1,324 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_MUTEX 0x0400 +#define KZT_MUTEX_NAME "mutex" +#define KZT_MUTEX_DESC "Kernel Mutex Tests" + +#define KZT_MUTEX_TEST1_ID 0x0401 +#define KZT_MUTEX_TEST1_NAME "tryenter" +#define KZT_MUTEX_TEST1_DESC "Validate mutex_tryenter() correctness" + +#define KZT_MUTEX_TEST2_ID 0x0402 +#define KZT_MUTEX_TEST2_NAME "race" +#define KZT_MUTEX_TEST2_DESC "Many threads entering/exiting the mutex" + +#define KZT_MUTEX_TEST3_ID 0x0403 +#define KZT_MUTEX_TEST3_NAME "owned" +#define KZT_MUTEX_TEST3_DESC "Validate mutex_owned() correctness" + +#define KZT_MUTEX_TEST4_ID 0x0404 +#define KZT_MUTEX_TEST4_NAME "owner" +#define KZT_MUTEX_TEST4_DESC "Validate mutex_owner() correctness" + +#define KZT_MUTEX_TEST_MAGIC 0x115599DDUL +#define KZT_MUTEX_TEST_NAME "mutex_test" +#define KZT_MUTEX_TEST_WORKQ "mutex_wq" +#define KZT_MUTEX_TEST_COUNT 128 + +typedef struct mutex_priv { + unsigned long mp_magic; + struct file *mp_file; + struct work_struct mp_work[KZT_MUTEX_TEST_COUNT]; + kmutex_t mp_mtx; + int mp_rc; +} mutex_priv_t; + + +static void +kzt_mutex_test1_work(void *priv) +{ + mutex_priv_t *mp = (mutex_priv_t *)priv; + + ASSERT(mp->mp_magic == KZT_MUTEX_TEST_MAGIC); + mp->mp_rc = 0; + + if (!mutex_tryenter(&mp->mp_mtx)) + mp->mp_rc = -EBUSY; +} + +static int +kzt_mutex_test1(struct file *file, void *arg) +{ + struct workqueue_struct *wq; + struct work_struct work; + mutex_priv_t *mp; + int rc = 0; + + mp = (mutex_priv_t *)kmalloc(sizeof(*mp), GFP_KERNEL); + if (mp == NULL) + return -ENOMEM; + + wq = create_singlethread_workqueue(KZT_MUTEX_TEST_WORKQ); + if (wq == NULL) { + rc = -ENOMEM; + goto out2; + } + + mutex_init(&(mp->mp_mtx), KZT_MUTEX_TEST_NAME, MUTEX_DEFAULT, NULL); + mutex_enter(&(mp->mp_mtx)); + + mp->mp_magic = KZT_MUTEX_TEST_MAGIC; + mp->mp_file = file; + INIT_WORK(&work, kzt_mutex_test1_work, mp); + + /* Schedule a work item which will try and aquire the mutex via + * mutex_tryenter() while its held. This should fail and the work + * item will indicte this status in the passed private data. */ + if (!queue_work(wq, &work)) { + mutex_exit(&(mp->mp_mtx)); + rc = -EINVAL; + goto out; + } + + flush_workqueue(wq); + mutex_exit(&(mp->mp_mtx)); + + /* Work item successfully aquired mutex, very bad! */ + if (mp->mp_rc != -EBUSY) { + rc = -EINVAL; + goto out; + } + + kzt_vprint(file, KZT_MUTEX_TEST1_NAME, "%s", + "mutex_trylock() correctly failed when mutex held\n"); + + /* Schedule a work item which will try and aquire the mutex via + * mutex_tryenter() while it is not held. This should work and + * the item will indicte this status in the passed private data. */ + if (!queue_work(wq, &work)) { + rc = -EINVAL; + goto out; + } + + flush_workqueue(wq); + + /* Work item failed to aquire mutex, very bad! */ + if (mp->mp_rc != 0) { + rc = -EINVAL; + goto out; + } + + kzt_vprint(file, KZT_MUTEX_TEST1_NAME, "%s", + "mutex_trylock() correctly succeeded when mutex unheld\n"); +out: + mutex_destroy(&(mp->mp_mtx)); + destroy_workqueue(wq); +out2: + kfree(mp); + + return rc; +} + +static void +kzt_mutex_test2_work(void *priv) +{ + mutex_priv_t *mp = (mutex_priv_t *)priv; + int rc; + + ASSERT(mp->mp_magic == KZT_MUTEX_TEST_MAGIC); + + /* Read the value before sleeping and write it after we wake up to + * maximize the chance of a race if mutexs are not working properly */ + mutex_enter(&mp->mp_mtx); + rc = mp->mp_rc; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ / 100); /* 1/100 of a second */ + mp->mp_rc = rc + 1; + mutex_exit(&mp->mp_mtx); +} + +static int +kzt_mutex_test2(struct file *file, void *arg) +{ + struct workqueue_struct *wq; + mutex_priv_t *mp; + int i, rc = 0; + + mp = (mutex_priv_t *)kmalloc(sizeof(*mp), GFP_KERNEL); + if (mp == NULL) + return -ENOMEM; + + /* Create a thread per CPU items on queue will race */ + wq = create_workqueue(KZT_MUTEX_TEST_WORKQ); + if (wq == NULL) { + rc = -ENOMEM; + goto out; + } + + mutex_init(&(mp->mp_mtx), KZT_MUTEX_TEST_NAME, MUTEX_DEFAULT, NULL); + + mp->mp_magic = KZT_MUTEX_TEST_MAGIC; + mp->mp_file = file; + mp->mp_rc = 0; + + /* Schedule N work items to the work queue each of which enters the + * mutex, sleeps briefly, then exits the mutex. On a multiprocessor + * box these work items will be handled by all available CPUs. The + * mutex is instrumented such that if any two processors are in the + * critical region at the same time the system will panic. If the + * mutex is implemented right this will never happy, that's a pass. */ + for (i = 0; i < KZT_MUTEX_TEST_COUNT; i++) { + INIT_WORK(&(mp->mp_work[i]), kzt_mutex_test2_work, mp); + + if (!queue_work(wq, &(mp->mp_work[i]))) { + kzt_vprint(file, KZT_MUTEX_TEST2_NAME, + "Failed to queue work id %d\n", i); + rc = -EINVAL; + } + } + + flush_workqueue(wq); + + if (mp->mp_rc == KZT_MUTEX_TEST_COUNT) { + kzt_vprint(file, KZT_MUTEX_TEST2_NAME, "%d racing threads " + "correctly entered/exited the mutex %d times\n", + num_online_cpus(), mp->mp_rc); + } else { + kzt_vprint(file, KZT_MUTEX_TEST2_NAME, "%d racing threads " + "only processed %d/%d mutex work items\n", + num_online_cpus(), mp->mp_rc, KZT_MUTEX_TEST_COUNT); + rc = -EINVAL; + } + + mutex_destroy(&(mp->mp_mtx)); + destroy_workqueue(wq); +out: + kfree(mp); + + return rc; +} + +static int +kzt_mutex_test3(struct file *file, void *arg) +{ + kmutex_t mtx; + int rc = 0; + + mutex_init(&mtx, KZT_MUTEX_TEST_NAME, MUTEX_DEFAULT, NULL); + + mutex_enter(&mtx); + + /* Mutex should be owned by current */ + if (!mutex_owned(&mtx)) { + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "Mutex should " + "be owned by pid %d but is owned by pid %d\n", + current->pid, mtx.km_owner ? mtx.km_owner->pid : -1); + rc = -EINVAL; + goto out; + } + + mutex_exit(&mtx); + + /* Mutex should not be owned by any task */ + if (mutex_owned(&mtx)) { + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "Mutex should " + "not be owned but is owned by pid %d\n", + mtx.km_owner ? mtx.km_owner->pid : -1); + rc = -EINVAL; + goto out; + } + + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "%s", + "Correct mutex_owned() behavior\n"); +out: + mutex_destroy(&mtx); + + return rc; +} + +static int +kzt_mutex_test4(struct file *file, void *arg) +{ + kmutex_t mtx; + kthread_t *owner; + int rc = 0; + + mutex_init(&mtx, KZT_MUTEX_TEST_NAME, MUTEX_DEFAULT, NULL); + + mutex_enter(&mtx); + + /* Mutex should be owned by current */ + owner = mutex_owner(&mtx); + if (current != owner) { + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "Mutex should " + "be owned by pid %d but is owned by pid %d\n", + current->pid, owner ? owner->pid : -1); + rc = -EINVAL; + goto out; + } + + mutex_exit(&mtx); + + /* Mutex should not be owned by any task */ + owner = mutex_owner(&mtx); + if (owner) { + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "Mutex should not " + "be owned but is owned by pid %d\n", owner->pid); + rc = -EINVAL; + goto out; + } + + kzt_vprint(file, KZT_MUTEX_TEST3_NAME, "%s", + "Correct mutex_owner() behavior\n"); +out: + mutex_destroy(&mtx); + + return rc; +} + +kzt_subsystem_t * +kzt_mutex_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_MUTEX_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_MUTEX_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_MUTEX; + + KZT_TEST_INIT(sub, KZT_MUTEX_TEST1_NAME, KZT_MUTEX_TEST1_DESC, + KZT_MUTEX_TEST1_ID, kzt_mutex_test1); + KZT_TEST_INIT(sub, KZT_MUTEX_TEST2_NAME, KZT_MUTEX_TEST2_DESC, + KZT_MUTEX_TEST2_ID, kzt_mutex_test2); + KZT_TEST_INIT(sub, KZT_MUTEX_TEST3_NAME, KZT_MUTEX_TEST3_DESC, + KZT_MUTEX_TEST3_ID, kzt_mutex_test3); + KZT_TEST_INIT(sub, KZT_MUTEX_TEST4_NAME, KZT_MUTEX_TEST4_DESC, + KZT_MUTEX_TEST4_ID, kzt_mutex_test4); + + return sub; +} + +void +kzt_mutex_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_MUTEX_TEST4_ID); + KZT_TEST_FINI(sub, KZT_MUTEX_TEST3_ID); + KZT_TEST_FINI(sub, KZT_MUTEX_TEST2_ID); + KZT_TEST_FINI(sub, KZT_MUTEX_TEST1_ID); + + kfree(sub); +} + +int +kzt_mutex_id(void) { + return KZT_SUBSYSTEM_MUTEX; +} diff --git a/src/splat/splat-random.c b/src/splat/splat-random.c new file mode 100644 index 000000000..abb654063 --- /dev/null +++ b/src/splat/splat-random.c @@ -0,0 +1,104 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_KRNG 0x0300 +#define KZT_KRNG_NAME "krng" +#define KZT_KRNG_DESC "Kernel Random Number Generator Tests" + +#define KZT_KRNG_TEST1_ID 0x0301 +#define KZT_KRNG_TEST1_NAME "freq" +#define KZT_KRNG_TEST1_DESC "Frequency Test" + +#define KRNG_NUM_BITS 1048576 +#define KRNG_NUM_BYTES (KRNG_NUM_BITS >> 3) +#define KRNG_NUM_BITS_DIV2 (KRNG_NUM_BITS >> 1) +#define KRNG_ERROR_RANGE 2097 + +/* Random Number Generator Tests + There can be meny more tests on quality of the + random number generator. For now we are only + testing the frequency of particular bits. + We could also test consecutive sequences, + randomness within a particular block, etc. + but is probably not necessary for our purposes */ + +static int +kzt_krng_test1(struct file *file, void *arg) +{ + uint8_t *buf; + int i, j, diff, num = 0, rc = 0; + + buf = kmalloc(sizeof(*buf) * KRNG_NUM_BYTES, GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto out; + } + + memset(buf, 0, sizeof(*buf) * KRNG_NUM_BYTES); + + /* Always succeeds */ + random_get_pseudo_bytes(buf, sizeof(uint8_t) * KRNG_NUM_BYTES); + + for (i = 0; i < KRNG_NUM_BYTES; i++) { + uint8_t tmp = buf[i]; + for (j = 0; j < 8; j++) { + uint8_t tmp2 = ((tmp >> j) & 0x01); + if (tmp2 == 1) { + num++; + } + } + } + + kfree(buf); + + diff = KRNG_NUM_BITS_DIV2 - num; + if (diff < 0) + diff *= -1; + + kzt_print(file, "Test 1 Number of ones: %d\n", num); + kzt_print(file, "Test 1 Difference from expected: %d Allowed: %d\n", + diff, KRNG_ERROR_RANGE); + + if (diff > KRNG_ERROR_RANGE) + rc = -ERANGE; +out: + return rc; +} + +kzt_subsystem_t * +kzt_krng_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_KRNG_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_KRNG_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_KRNG; + + KZT_TEST_INIT(sub, KZT_KRNG_TEST1_NAME, KZT_KRNG_TEST1_DESC, + KZT_KRNG_TEST1_ID, kzt_krng_test1); + + return sub; +} + +void +kzt_krng_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + + KZT_TEST_FINI(sub, KZT_KRNG_TEST1_ID); + + kfree(sub); +} + +int +kzt_krng_id(void) { + return KZT_SUBSYSTEM_KRNG; +} diff --git a/src/splat/splat-rwlock.c b/src/splat/splat-rwlock.c new file mode 100644 index 000000000..9820937c3 --- /dev/null +++ b/src/splat/splat-rwlock.c @@ -0,0 +1,764 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_RWLOCK 0x0700 +#define KZT_RWLOCK_NAME "rwlock" +#define KZT_RWLOCK_DESC "Kernel RW Lock Tests" + +#define KZT_RWLOCK_TEST1_ID 0x0701 +#define KZT_RWLOCK_TEST1_NAME "rwtest1" +#define KZT_RWLOCK_TEST1_DESC "Multiple Readers One Writer" + +#define KZT_RWLOCK_TEST2_ID 0x0702 +#define KZT_RWLOCK_TEST2_NAME "rwtest2" +#define KZT_RWLOCK_TEST2_DESC "Multiple Writers" + +#define KZT_RWLOCK_TEST3_ID 0x0703 +#define KZT_RWLOCK_TEST3_NAME "rwtest3" +#define KZT_RWLOCK_TEST3_DESC "Owner Verification" + +#define KZT_RWLOCK_TEST4_ID 0x0704 +#define KZT_RWLOCK_TEST4_NAME "rwtest4" +#define KZT_RWLOCK_TEST4_DESC "Trylock Test" + +#define KZT_RWLOCK_TEST5_ID 0x0705 +#define KZT_RWLOCK_TEST5_NAME "rwtest5" +#define KZT_RWLOCK_TEST5_DESC "Write Downgrade Test" + +#define KZT_RWLOCK_TEST6_ID 0x0706 +#define KZT_RWLOCK_TEST6_NAME "rwtest6" +#define KZT_RWLOCK_TEST6_DESC "Read Upgrade Test" + +#define KZT_RWLOCK_TEST_MAGIC 0x115599DDUL +#define KZT_RWLOCK_TEST_NAME "rwlock_test" +#define KZT_RWLOCK_TEST_COUNT 8 + +#define KZT_RWLOCK_RELEASE_INIT 0 +#define KZT_RWLOCK_RELEASE_WRITERS 1 +#define KZT_RWLOCK_RELEASE_READERS 2 + +typedef struct rw_priv { + unsigned long rw_magic; + struct file *rw_file; + krwlock_t rwl; + spinlock_t rw_priv_lock; + wait_queue_head_t rw_waitq; + atomic_t rw_completed; + atomic_t rw_acquired; + atomic_t rw_waiters; + atomic_t rw_release; +} rw_priv_t; + +typedef struct rw_thr { + int rwt_id; + const char *rwt_name; + rw_priv_t *rwt_rwp; + int rwt_rc; +} rw_thr_t; + +static inline void +kzt_rwlock_sleep(signed long delay) +{ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(delay); +} + +#define kzt_rwlock_lock_and_test(lock,test) \ +({ \ + int ret = 0; \ + \ + spin_lock(lock); \ + ret = (test) ? 1 : 0; \ + spin_unlock(lock); \ + ret; \ +}) + +void kzt_init_rw_priv(rw_priv_t *rwv, struct file *file) +{ + rwv->rw_magic = KZT_RWLOCK_TEST_MAGIC; + rwv->rw_file = file; + spin_lock_init(&rwv->rw_priv_lock); + init_waitqueue_head(&rwv->rw_waitq); + atomic_set(&rwv->rw_completed, 0); + atomic_set(&rwv->rw_acquired, 0); + atomic_set(&rwv->rw_waiters, 0); + atomic_set(&rwv->rw_release, KZT_RWLOCK_RELEASE_INIT); + + /* Initialize the read/write lock */ + rw_init(&rwv->rwl, KZT_RWLOCK_TEST_NAME, RW_DEFAULT, NULL); +} + +int +kzt_rwlock_test1_writer_thread(void *arg) +{ + rw_thr_t *rwt = (rw_thr_t *)arg; + rw_priv_t *rwv = rwt->rwt_rwp; + uint8_t rnd = 0; + char name[16]; + + ASSERT(rwv->rw_magic == KZT_RWLOCK_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", + KZT_RWLOCK_TEST_NAME, rwt->rwt_id); + daemonize(name); + get_random_bytes((void *)&rnd, 1); + kzt_rwlock_sleep(rnd * HZ / 1000); + + spin_lock(&rwv->rw_priv_lock); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread trying to acquire rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + atomic_inc(&rwv->rw_waiters); + spin_unlock(&rwv->rw_priv_lock); + + /* Take the semaphore for writing + * release it when we are told to */ + rw_enter(&rwv->rwl, RW_WRITER); + + spin_lock(&rwv->rw_priv_lock); + atomic_dec(&rwv->rw_waiters); + atomic_inc(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread acquired rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Wait here until the control thread + * says we can release the write lock */ + wait_event_interruptible(rwv->rw_waitq, + kzt_rwlock_lock_and_test(&rwv->rw_priv_lock, + atomic_read(&rwv->rw_release) == + KZT_RWLOCK_RELEASE_WRITERS)); + spin_lock(&rwv->rw_priv_lock); + atomic_inc(&rwv->rw_completed); + atomic_dec(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread dropped rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Release the semaphore */ + rw_exit(&rwv->rwl); + return 0; +} + +int +kzt_rwlock_test1_reader_thread(void *arg) +{ + rw_thr_t *rwt = (rw_thr_t *)arg; + rw_priv_t *rwv = rwt->rwt_rwp; + uint8_t rnd = 0; + char name[16]; + + ASSERT(rwv->rw_magic == KZT_RWLOCK_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", + KZT_RWLOCK_TEST_NAME, rwt->rwt_id); + daemonize(name); + get_random_bytes((void *)&rnd, 1); + kzt_rwlock_sleep(rnd * HZ / 1000); + + /* Don't try and and take the semaphore until + * someone else has already acquired it */ + wait_event_interruptible(rwv->rw_waitq, + kzt_rwlock_lock_and_test(&rwv->rw_priv_lock, + atomic_read(&rwv->rw_acquired) > 0)); + + spin_lock(&rwv->rw_priv_lock); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread trying to acquire rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + atomic_inc(&rwv->rw_waiters); + spin_unlock(&rwv->rw_priv_lock); + + /* Take the semaphore for reading + * release it when we are told to */ + rw_enter(&rwv->rwl, RW_READER); + + spin_lock(&rwv->rw_priv_lock); + atomic_dec(&rwv->rw_waiters); + atomic_inc(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread acquired rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Wait here until the control thread + * says we can release the read lock */ + wait_event_interruptible(rwv->rw_waitq, + kzt_rwlock_lock_and_test(&rwv->rw_priv_lock, + atomic_read(&rwv->rw_release) == + KZT_RWLOCK_RELEASE_READERS)); + + spin_lock(&rwv->rw_priv_lock); + atomic_inc(&rwv->rw_completed); + atomic_dec(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread dropped rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Release the semaphore */ + rw_exit(&rwv->rwl); + return 0; +} + +static int +kzt_rwlock_test1(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_RWLOCK_TEST_COUNT]; + rw_thr_t rwt[KZT_RWLOCK_TEST_COUNT]; + rw_priv_t rwv; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, 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 < KZT_RWLOCK_TEST_COUNT; i++) { + rwt[i].rwt_rwp = &rwv; + rwt[i].rwt_id = i; + rwt[i].rwt_name = KZT_RWLOCK_TEST1_NAME; + rwt[i].rwt_rc = 0; + + /* The first thread will be a writer */ + if (i == 0) { + pids[i] = kernel_thread(kzt_rwlock_test1_writer_thread, + &rwt[i], 0); + } else { + pids[i] = kernel_thread(kzt_rwlock_test1_reader_thread, + &rwt[i], 0); + } + + if (pids[i] >= 0) { + count++; + } + } + + /* Once the writer has the lock, release the readers */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, atomic_read(&rwv.rw_acquired) <= 0)) { + kzt_rwlock_sleep(1 * HZ); + } + wake_up_interruptible(&rwv.rw_waitq); + + /* Ensure that there is only 1 writer and all readers are waiting */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) != 1 || + atomic_read(&rwv.rw_waiters) != + KZT_RWLOCK_TEST_COUNT - 1)) { + + kzt_rwlock_sleep(1 * HZ); + } + /* Relase the writer */ + spin_lock(&rwv.rw_priv_lock); + atomic_set(&rwv.rw_release, KZT_RWLOCK_RELEASE_WRITERS); + spin_unlock(&rwv.rw_priv_lock); + wake_up_interruptible(&rwv.rw_waitq); + + /* Now ensure that there are multiple reader threads holding the lock */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) <= 1)) { + kzt_rwlock_sleep(1 * HZ); + } + /* Release the readers */ + spin_lock(&rwv.rw_priv_lock); + atomic_set(&rwv.rw_release, KZT_RWLOCK_RELEASE_READERS); + spin_unlock(&rwv.rw_priv_lock); + wake_up_interruptible(&rwv.rw_waitq); + + /* Wait for the test to complete */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) != 0 || + atomic_read(&rwv.rw_waiters) != 0)) { + kzt_rwlock_sleep(1 * HZ); + + } + + rw_destroy(&rwv.rwl); + return rc; +} + +int +kzt_rwlock_test2_writer_thread(void *arg) +{ + rw_thr_t *rwt = (rw_thr_t *)arg; + rw_priv_t *rwv = rwt->rwt_rwp; + uint8_t rnd = 0; + char name[16]; + + ASSERT(rwv->rw_magic == KZT_RWLOCK_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", + KZT_RWLOCK_TEST_NAME, rwt->rwt_id); + daemonize(name); + get_random_bytes((void *)&rnd, 1); + kzt_rwlock_sleep(rnd * HZ / 1000); + + /* Here just increment the waiters count even if we are not + * exactly about to call rw_enter(). Not really a big deal + * since more than likely will be true when we simulate work + * later on */ + spin_lock(&rwv->rw_priv_lock); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread trying to acquire rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + atomic_inc(&rwv->rw_waiters); + spin_unlock(&rwv->rw_priv_lock); + + /* Wait here until the control thread + * says we can acquire the write lock */ + wait_event_interruptible(rwv->rw_waitq, + kzt_rwlock_lock_and_test(&rwv->rw_priv_lock, + atomic_read(&rwv->rw_release) == + KZT_RWLOCK_RELEASE_WRITERS)); + + /* Take the semaphore for writing */ + rw_enter(&rwv->rwl, RW_WRITER); + + spin_lock(&rwv->rw_priv_lock); + atomic_dec(&rwv->rw_waiters); + atomic_inc(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread acquired rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Give up the processor for a bit to simulate + * doing some work while taking the write lock */ + kzt_rwlock_sleep(rnd * HZ / 1000); + + /* Ensure that we are the only one writing */ + if (atomic_read(&rwv->rw_acquired) > 1) { + rwt->rwt_rc = 1; + } else { + rwt->rwt_rc = 0; + } + + spin_lock(&rwv->rw_priv_lock); + atomic_inc(&rwv->rw_completed); + atomic_dec(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s writer thread dropped rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + rw_exit(&rwv->rwl); + + + return 0; +} + +static int +kzt_rwlock_test2(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_RWLOCK_TEST_COUNT]; + rw_thr_t rwt[KZT_RWLOCK_TEST_COUNT]; + rw_priv_t rwv; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, 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 < KZT_RWLOCK_TEST_COUNT; i++) { + rwt[i].rwt_rwp = &rwv; + rwt[i].rwt_id = i; + rwt[i].rwt_name = KZT_RWLOCK_TEST2_NAME; + rwt[i].rwt_rc = 0; + + /* The first thread will be a writer */ + pids[i] = kernel_thread(kzt_rwlock_test2_writer_thread, + &rwt[i], 0); + + if (pids[i] >= 0) { + count++; + } + } + + /* Wait for writers to get queued up */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_waiters) < KZT_RWLOCK_TEST_COUNT)) { + kzt_rwlock_sleep(1 * HZ); + } + /* Relase the writers */ + spin_lock(&rwv.rw_priv_lock); + atomic_set(&rwv.rw_release, KZT_RWLOCK_RELEASE_WRITERS); + spin_unlock(&rwv.rw_priv_lock); + wake_up_interruptible(&rwv.rw_waitq); + + /* Wait for the test to complete */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) != 0 || + atomic_read(&rwv.rw_waiters) != 0)) { + kzt_rwlock_sleep(1 * HZ); + } + + /* If any of the write threads ever acquired the lock + * while another thread had it, make sure we return + * an error */ + for (i = 0; i < KZT_RWLOCK_TEST_COUNT; i++) { + if (rwt[i].rwt_rc) { + rc++; + } + } + + rw_destroy(&rwv.rwl); + return rc; +} + +static int +kzt_rwlock_test3(struct file *file, void *arg) +{ + kthread_t *owner; + rw_priv_t rwv; + int rc = 0; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, file); + + /* Take the rwlock for writing */ + rw_enter(&rwv.rwl, RW_WRITER); + owner = rw_owner(&rwv.rwl); + if (current != owner) { + kzt_vprint(file, KZT_RWLOCK_TEST3_NAME, "rwlock should " + "be owned by pid %d but is owned by pid %d\n", + current->pid, owner ? owner->pid : -1); + rc = -EINVAL; + goto out; + } + + /* Release the rwlock */ + rw_exit(&rwv.rwl); + owner = rw_owner(&rwv.rwl); + if (owner) { + kzt_vprint(file, KZT_RWLOCK_TEST3_NAME, "rwlock should not " + "be owned but is owned by pid %d\n", owner->pid); + rc = -EINVAL; + goto out; + } + + /* Take the rwlock for reading. + * Should not have an owner */ + rw_enter(&rwv.rwl, RW_READER); + owner = rw_owner(&rwv.rwl); + if (owner) { + kzt_vprint(file, KZT_RWLOCK_TEST3_NAME, "rwlock should not " + "be owned but is owned by pid %d\n", owner->pid); + /* Release the rwlock */ + rw_exit(&rwv.rwl); + rc = -EINVAL; + goto out; + } + + /* Release the rwlock */ + rw_exit(&rwv.rwl); + +out: + rw_destroy(&rwv.rwl); + return rc; +} + +int +kzt_rwlock_test4_reader_thread(void *arg) +{ + rw_thr_t *rwt = (rw_thr_t *)arg; + rw_priv_t *rwv = rwt->rwt_rwp; + uint8_t rnd = 0; + char name[16]; + + ASSERT(rwv->rw_magic == KZT_RWLOCK_TEST_MAGIC); + snprintf(name, sizeof(name), "%s%d", + KZT_RWLOCK_TEST_NAME, rwt->rwt_id); + daemonize(name); + get_random_bytes((void *)&rnd, 1); + kzt_rwlock_sleep(rnd * HZ / 1000); + + /* Don't try and and take the semaphore until + * someone else has already acquired it */ + wait_event_interruptible(rwv->rw_waitq, + kzt_rwlock_lock_and_test(&rwv->rw_priv_lock, + atomic_read(&rwv->rw_acquired) > 0)); + + spin_lock(&rwv->rw_priv_lock); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread trying to acquire rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Take the semaphore for reading + * release it when we are told to */ + rwt->rwt_rc = rw_tryenter(&rwv->rwl, RW_READER); + + /* Here we acquired the lock this is a + * failure since the writer should be + * holding the lock */ + if (rwt->rwt_rc == 1) { + spin_lock(&rwv->rw_priv_lock); + atomic_inc(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread acquired rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + spin_lock(&rwv->rw_priv_lock); + atomic_dec(&rwv->rw_acquired); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread dropped rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + + /* Release the semaphore */ + rw_exit(&rwv->rwl); + } + /* Here we know we didn't block and didn't + * acquire the rwlock for reading */ + else { + spin_lock(&rwv->rw_priv_lock); + atomic_inc(&rwv->rw_completed); + kzt_vprint(rwv->rw_file, rwt->rwt_name, + "%s reader thread could not acquire rwlock with " + "%d holding lock and %d waiting\n", + name, atomic_read(&rwv->rw_acquired), + atomic_read(&rwv->rw_waiters)); + spin_unlock(&rwv->rw_priv_lock); + } + + return 0; +} + +static int +kzt_rwlock_test4(struct file *file, void *arg) +{ + int i, count = 0, rc = 0; + long pids[KZT_RWLOCK_TEST_COUNT]; + rw_thr_t rwt[KZT_RWLOCK_TEST_COUNT]; + rw_priv_t rwv; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, 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 < KZT_RWLOCK_TEST_COUNT; i++) { + rwt[i].rwt_rwp = &rwv; + rwt[i].rwt_id = i; + rwt[i].rwt_name = KZT_RWLOCK_TEST4_NAME; + rwt[i].rwt_rc = 0; + + /* The first thread will be a writer */ + if (i == 0) { + /* We can reuse the test1 writer thread here */ + pids[i] = kernel_thread(kzt_rwlock_test1_writer_thread, + &rwt[i], 0); + } else { + pids[i] = kernel_thread(kzt_rwlock_test4_reader_thread, + &rwt[i], 0); + } + + if (pids[i] >= 0) { + count++; + } + } + + /* Once the writer has the lock, release the readers */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) <= 0)) { + kzt_rwlock_sleep(1 * HZ); + } + wake_up_interruptible(&rwv.rw_waitq); + + /* Make sure that the reader threads complete */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_completed) != KZT_RWLOCK_TEST_COUNT - 1)) { + kzt_rwlock_sleep(1 * HZ); + } + /* Release the writer */ + spin_lock(&rwv.rw_priv_lock); + atomic_set(&rwv.rw_release, KZT_RWLOCK_RELEASE_WRITERS); + spin_unlock(&rwv.rw_priv_lock); + wake_up_interruptible(&rwv.rw_waitq); + + /* Wait for the test to complete */ + while (kzt_rwlock_lock_and_test(&rwv.rw_priv_lock, + atomic_read(&rwv.rw_acquired) != 0 || + atomic_read(&rwv.rw_waiters) != 0)) { + kzt_rwlock_sleep(1 * HZ); + } + + /* If any of the reader threads ever acquired the lock + * while another thread had it, make sure we return + * an error since the rw_tryenter() should have failed */ + for (i = 0; i < KZT_RWLOCK_TEST_COUNT; i++) { + if (rwt[i].rwt_rc) { + rc++; + } + } + + rw_destroy(&rwv.rwl); + return rc; +} + +static int +kzt_rwlock_test5(struct file *file, void *arg) +{ + kthread_t *owner; + rw_priv_t rwv; + int rc = 0; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, file); + + /* Take the rwlock for writing */ + rw_enter(&rwv.rwl, RW_WRITER); + owner = rw_owner(&rwv.rwl); + if (current != owner) { + kzt_vprint(file, KZT_RWLOCK_TEST5_NAME, "rwlock should " + "be owned by pid %d but is owned by pid %d\n", + current->pid, owner ? owner->pid : -1); + rc = -EINVAL; + goto out; + } + + /* Make sure that the downgrade + * worked properly */ + rw_downgrade(&rwv.rwl); + + owner = rw_owner(&rwv.rwl); + if (owner) { + kzt_vprint(file, KZT_RWLOCK_TEST5_NAME, "rwlock should not " + "be owned but is owned by pid %d\n", owner->pid); + /* Release the rwlock */ + rw_exit(&rwv.rwl); + rc = -EINVAL; + goto out; + } + + /* Release the rwlock */ + rw_exit(&rwv.rwl); + +out: + rw_destroy(&rwv.rwl); + return rc; +} + +static int +kzt_rwlock_test6(struct file *file, void *arg) +{ + kthread_t *owner; + rw_priv_t rwv; + int rc = 0; + + /* Initialize private data + * including the rwlock */ + kzt_init_rw_priv(&rwv, file); + + /* Take the rwlock for reading */ + rw_enter(&rwv.rwl, RW_READER); + owner = rw_owner(&rwv.rwl); + if (owner) { + kzt_vprint(file, KZT_RWLOCK_TEST6_NAME, "rwlock should not " + "be owned but is owned by pid %d\n", owner->pid); + rc = -EINVAL; + goto out; + } + + /* Make sure that the upgrade + * worked properly */ + rc = !rw_tryupgrade(&rwv.rwl); + + owner = rw_owner(&rwv.rwl); + if (rc || current != owner) { + kzt_vprint(file, KZT_RWLOCK_TEST6_NAME, "rwlock should " + "be owned by pid %d but is owned by pid %d " + "trylock rc %d\n", + current->pid, owner ? owner->pid : -1, rc); + rc = -EINVAL; + goto out; + } + + /* Release the rwlock */ + rw_exit(&rwv.rwl); + +out: + rw_destroy(&rwv.rwl); + return rc; +} + +kzt_subsystem_t * +kzt_rwlock_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_RWLOCK_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_RWLOCK_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_RWLOCK; + + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST1_NAME, KZT_RWLOCK_TEST1_DESC, + KZT_RWLOCK_TEST1_ID, kzt_rwlock_test1); + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST2_NAME, KZT_RWLOCK_TEST2_DESC, + KZT_RWLOCK_TEST2_ID, kzt_rwlock_test2); + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST3_NAME, KZT_RWLOCK_TEST3_DESC, + KZT_RWLOCK_TEST3_ID, kzt_rwlock_test3); + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST4_NAME, KZT_RWLOCK_TEST4_DESC, + KZT_RWLOCK_TEST4_ID, kzt_rwlock_test4); + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST5_NAME, KZT_RWLOCK_TEST5_DESC, + KZT_RWLOCK_TEST5_ID, kzt_rwlock_test5); + KZT_TEST_INIT(sub, KZT_RWLOCK_TEST6_NAME, KZT_RWLOCK_TEST6_DESC, + KZT_RWLOCK_TEST6_ID, kzt_rwlock_test6); + + return sub; +} + +void +kzt_rwlock_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST6_ID); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST5_ID); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST4_ID); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST3_ID); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST2_ID); + KZT_TEST_FINI(sub, KZT_RWLOCK_TEST1_ID); + kfree(sub); +} + +int +kzt_rwlock_id(void) { + return KZT_SUBSYSTEM_RWLOCK; +} diff --git a/src/splat/splat-taskq.c b/src/splat/splat-taskq.c new file mode 100644 index 000000000..614e7136c --- /dev/null +++ b/src/splat/splat-taskq.c @@ -0,0 +1,238 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_TASKQ 0x0200 +#define KZT_TASKQ_NAME "taskq" +#define KZT_TASKQ_DESC "Kernel Task Queue Tests" + +#define KZT_TASKQ_TEST1_ID 0x0201 +#define KZT_TASKQ_TEST1_NAME "single" +#define KZT_TASKQ_TEST1_DESC "Single task queue, single task" + +#define KZT_TASKQ_TEST2_ID 0x0202 +#define KZT_TASKQ_TEST2_NAME "multiple" +#define KZT_TASKQ_TEST2_DESC "Multiple task queues, multiple tasks" + +typedef struct kzt_taskq_arg { + int flag; + int id; + struct file *file; + const char *name; +} kzt_taskq_arg_t; + +/* Validation Test 1 - Create a taskq, queue a task, wait until + * task completes, ensure task ran properly, cleanup taskq, + */ +static void +kzt_taskq_test1_func(void *arg) +{ + kzt_taskq_arg_t *tq_arg = (kzt_taskq_arg_t *)arg; + + ASSERT(tq_arg); + kzt_vprint(tq_arg->file, KZT_TASKQ_TEST1_NAME, + "Taskq '%s' function '%s' setting flag\n", + tq_arg->name, sym2str(kzt_taskq_test1_func)); + tq_arg->flag = 1; +} + +static int +kzt_taskq_test1(struct file *file, void *arg) +{ + taskq_t *tq; + taskqid_t id; + kzt_taskq_arg_t tq_arg; + + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, "Taskq '%s' creating\n", + KZT_TASKQ_TEST1_NAME); + if ((tq = taskq_create(KZT_TASKQ_TEST1_NAME, 1, 0, 0, 0, 0)) == NULL) { + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, + "Taskq '%s' create failed\n", + KZT_TASKQ_TEST1_NAME); + return -EINVAL; + } + + tq_arg.flag = 0; + tq_arg.id = 0; + tq_arg.file = file; + tq_arg.name = KZT_TASKQ_TEST1_NAME; + + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, + "Taskq '%s' function '%s' dispatching\n", + tq_arg.name, sym2str(kzt_taskq_test1_func)); + if ((id = taskq_dispatch(tq, kzt_taskq_test1_func, &tq_arg, 0)) == 0) { + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, + "Taskq '%s' function '%s' dispatch failed\n", + tq_arg.name, sym2str(kzt_taskq_test1_func)); + taskq_destory(tq); + return -EINVAL; + } + + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, "Taskq '%s' waiting\n", + tq_arg.name); + taskq_wait(tq); + kzt_vprint(file, KZT_TASKQ_TEST1_NAME, "Taskq '%s' destroying\n", + tq_arg.name); + taskq_destory(tq); + + return (tq_arg.flag) ? 0 : -EINVAL; +} + +/* Validation Test 2 - Create multiple taskq's, each with multiple tasks, + * wait until all tasks complete, ensure all tasks ran properly and in the + * the correct order, cleanup taskq's + */ +static void +kzt_taskq_test2_func1(void *arg) +{ + kzt_taskq_arg_t *tq_arg = (kzt_taskq_arg_t *)arg; + + ASSERT(tq_arg); + kzt_vprint(tq_arg->file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' flag = %d = %d * 2\n", + tq_arg->name, tq_arg->id, + sym2str(kzt_taskq_test2_func1), + tq_arg->flag * 2, tq_arg->flag); + tq_arg->flag *= 2; +} + +static void +kzt_taskq_test2_func2(void *arg) +{ + kzt_taskq_arg_t *tq_arg = (kzt_taskq_arg_t *)arg; + + ASSERT(tq_arg); + kzt_vprint(tq_arg->file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' flag = %d = %d + 1\n", + tq_arg->name, tq_arg->id, + sym2str(kzt_taskq_test2_func2), + tq_arg->flag + 1, tq_arg->flag); + tq_arg->flag += 1; +} + +#define TEST2_TASKQS 8 +static int +kzt_taskq_test2(struct file *file, void *arg) { + taskq_t *tq[TEST2_TASKQS] = { NULL }; + taskqid_t id; + kzt_taskq_arg_t tq_args[TEST2_TASKQS]; + int i, rc = 0; + + for (i = 0; i < TEST2_TASKQS; i++) { + + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, "Taskq '%s/%d' " + "creating\n", KZT_TASKQ_TEST2_NAME, i); + if ((tq[i] = taskq_create(KZT_TASKQ_TEST2_NAME, + 1, 0, 0, 0, 0)) == NULL) { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' create failed\n", + KZT_TASKQ_TEST2_NAME, i); + rc = -EINVAL; + break; + } + + tq_args[i].flag = i; + tq_args[i].id = i; + tq_args[i].file = file; + tq_args[i].name = KZT_TASKQ_TEST2_NAME; + + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' dispatching\n", + tq_args[i].name, tq_args[i].id, + sym2str(kzt_taskq_test2_func1)); + if ((id = taskq_dispatch( + tq[i], kzt_taskq_test2_func1, &tq_args[i], 0)) == 0) { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' dispatch " + "failed\n", tq_args[i].name, tq_args[i].id, + sym2str(kzt_taskq_test2_func1)); + rc = -EINVAL; + break; + } + + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' dispatching\n", + tq_args[i].name, tq_args[i].id, + sym2str(kzt_taskq_test2_func2)); + if ((id = taskq_dispatch( + tq[i], kzt_taskq_test2_func2, &tq_args[i], 0)) == 0) { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' function '%s' dispatch failed\n", + tq_args[i].name, tq_args[i].id, + sym2str(kzt_taskq_test2_func2)); + rc = -EINVAL; + break; + } + } + + /* When rc is set we're effectively just doing cleanup here, so + * ignore new errors in that case. They just cause noise. */ + for (i = 0; i < TEST2_TASKQS; i++) { + if (tq[i] != NULL) { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' waiting\n", + tq_args[i].name, tq_args[i].id); + taskq_wait(tq[i]); + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d; destroying\n", + tq_args[i].name, tq_args[i].id); + taskq_destory(tq[i]); + + if (!rc && tq_args[i].flag != ((i * 2) + 1)) { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' processed tasks " + "out of order; %d != %d\n", + tq_args[i].name, tq_args[i].id, + tq_args[i].flag, i * 2 + 1); + rc = -EINVAL; + } else { + kzt_vprint(file, KZT_TASKQ_TEST2_NAME, + "Taskq '%s/%d' processed tasks " + "in the correct order; %d == %d\n", + tq_args[i].name, tq_args[i].id, + tq_args[i].flag, i * 2 + 1); + } + } + } + + return rc; +} + +kzt_subsystem_t * +kzt_taskq_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_TASKQ_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_TASKQ_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_TASKQ; + + KZT_TEST_INIT(sub, KZT_TASKQ_TEST1_NAME, KZT_TASKQ_TEST1_DESC, + KZT_TASKQ_TEST1_ID, kzt_taskq_test1); + KZT_TEST_INIT(sub, KZT_TASKQ_TEST2_NAME, KZT_TASKQ_TEST2_DESC, + KZT_TASKQ_TEST2_ID, kzt_taskq_test2); + + return sub; +} + +void +kzt_taskq_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_TASKQ_TEST2_ID); + KZT_TEST_FINI(sub, KZT_TASKQ_TEST1_ID); + + kfree(sub); +} + +int +kzt_taskq_id(void) { + return KZT_SUBSYSTEM_TASKQ; +} diff --git a/src/splat/splat-thread.c b/src/splat/splat-thread.c new file mode 100644 index 000000000..0741db1fa --- /dev/null +++ b/src/splat/splat-thread.c @@ -0,0 +1,116 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_THREAD 0x0600 +#define KZT_THREAD_NAME "thread" +#define KZT_THREAD_DESC "Kernel Thread Tests" + +#define KZT_THREAD_TEST1_ID 0x0601 +#define KZT_THREAD_TEST1_NAME "create" +#define KZT_THREAD_TEST1_DESC "Validate thread creation and destruction" + +#define KZT_THREAD_TEST_MAGIC 0x4488CC00UL + +typedef struct thread_priv { + unsigned long tp_magic; + struct file *tp_file; + spinlock_t tp_lock; + wait_queue_head_t tp_waitq; + int tp_rc; +} thread_priv_t; + + +static void +kzt_thread_work(void *priv) +{ + thread_priv_t *tp = (thread_priv_t *)priv; + + spin_lock(&tp->tp_lock); + ASSERT(tp->tp_magic == KZT_THREAD_TEST_MAGIC); + tp->tp_rc = 1; + + spin_unlock(&tp->tp_lock); + wake_up(&tp->tp_waitq); + + thread_exit(); +} + +static int +kzt_thread_test1(struct file *file, void *arg) +{ + thread_priv_t tp; + DEFINE_WAIT(wait); + kthread_t *thr; + int rc = 0; + + tp.tp_magic = KZT_THREAD_TEST_MAGIC; + tp.tp_file = file; + spin_lock_init(&tp.tp_lock); + init_waitqueue_head(&tp.tp_waitq); + tp.tp_rc = 0; + + spin_lock(&tp.tp_lock); + + thr = (kthread_t *)thread_create(NULL, 0, kzt_thread_work, &tp, 0, + (proc_t *) &p0, TS_RUN, minclsyspri); + /* Must never fail under Solaris, but we check anyway so we can + * report an error when this impossible thing happens */ + if (thr == NULL) { + rc = -ESRCH; + goto out; + } + + for (;;) { + prepare_to_wait(&tp.tp_waitq, &wait, TASK_UNINTERRUPTIBLE); + if (tp.tp_rc) + break; + + spin_unlock(&tp.tp_lock); + schedule(); + spin_lock(&tp.tp_lock); + } + + kzt_vprint(file, KZT_THREAD_TEST1_NAME, "%s", + "Thread successfully started and exited cleanly\n"); +out: + spin_unlock(&tp.tp_lock); + + return rc; +} + +kzt_subsystem_t * +kzt_thread_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_THREAD_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_THREAD_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_THREAD; + + KZT_TEST_INIT(sub, KZT_THREAD_TEST1_NAME, KZT_THREAD_TEST1_DESC, + KZT_THREAD_TEST1_ID, kzt_thread_test1); + + return sub; +} + +void +kzt_thread_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + KZT_TEST_FINI(sub, KZT_THREAD_TEST1_ID); + + kfree(sub); +} + +int +kzt_thread_id(void) { + return KZT_SUBSYSTEM_THREAD; +} diff --git a/src/splat/splat-time.c b/src/splat/splat-time.c new file mode 100644 index 000000000..b7d4ce86e --- /dev/null +++ b/src/splat/splat-time.c @@ -0,0 +1,90 @@ +#include <sys/zfs_context.h> +#include <sys/splat-ctl.h> + +#define KZT_SUBSYSTEM_TIME 0x0800 +#define KZT_TIME_NAME "time" +#define KZT_TIME_DESC "Kernel Time Tests" + +#define KZT_TIME_TEST1_ID 0x0801 +#define KZT_TIME_TEST1_NAME "time1" +#define KZT_TIME_TEST1_DESC "HZ Test" + +#define KZT_TIME_TEST2_ID 0x0802 +#define KZT_TIME_TEST2_NAME "time2" +#define KZT_TIME_TEST2_DESC "Monotonic Test" + +static int +kzt_time_test1(struct file *file, void *arg) +{ + int myhz = hz; + kzt_vprint(file, KZT_TIME_TEST1_NAME, "hz is %d\n", myhz); + return 0; +} + +static int +kzt_time_test2(struct file *file, void *arg) +{ + hrtime_t tm1, tm2; + int i; + + tm1 = gethrtime(); + kzt_vprint(file, KZT_TIME_TEST2_NAME, "time is %lld\n", tm1); + + for(i = 0; i < 100; i++) { + tm2 = gethrtime(); + kzt_vprint(file, KZT_TIME_TEST2_NAME, "time is %lld\n", tm2); + + if(tm1 > tm2) { + kzt_print(file, "%s: gethrtime() is not giving monotonically increasing values\n", KZT_TIME_TEST2_NAME); + return 1; + } + tm1 = tm2; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(10); + } + + return 0; +} + +kzt_subsystem_t * +kzt_time_init(void) +{ + kzt_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, KZT_TIME_NAME, KZT_NAME_SIZE); + strncpy(sub->desc.desc, KZT_TIME_DESC, KZT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = KZT_SUBSYSTEM_TIME; + + KZT_TEST_INIT(sub, KZT_TIME_TEST1_NAME, KZT_TIME_TEST1_DESC, + KZT_TIME_TEST1_ID, kzt_time_test1); + KZT_TEST_INIT(sub, KZT_TIME_TEST2_NAME, KZT_TIME_TEST2_DESC, + KZT_TIME_TEST2_ID, kzt_time_test2); + + return sub; +} + +void +kzt_time_fini(kzt_subsystem_t *sub) +{ + ASSERT(sub); + + KZT_TEST_FINI(sub, KZT_TIME_TEST2_ID); + KZT_TEST_FINI(sub, KZT_TIME_TEST1_ID); + + kfree(sub); +} + +int +kzt_time_id(void) +{ + return KZT_SUBSYSTEM_TIME; +} |