diff --git a/include/splat-ctl.h b/include/splat-ctl.h index 6f15ba2e8f..b800887b8f 100644 --- a/include/splat-ctl.h +++ b/include/splat-ctl.h @@ -105,6 +105,7 @@ typedef struct splat_cmd { #define SPLAT_SUBSYSTEM_GENERIC 0x0d00 #define SPLAT_SUBSYSTEM_CRED 0x0e00 #define SPLAT_SUBSYSTEM_ZLIB 0x0f00 +#define SPLAT_SUBSYSTEM_LINUX 0x1000 #define SPLAT_SUBSYSTEM_UNKNOWN 0xff00 #endif /* _SPLAT_CTL_H */ diff --git a/module/splat/Makefile.in b/module/splat/Makefile.in index 2bf25c5b2c..8340b7036f 100644 --- a/module/splat/Makefile.in +++ b/module/splat/Makefile.in @@ -24,3 +24,4 @@ splat-objs += @top_srcdir@/module/splat/splat-list.o splat-objs += @top_srcdir@/module/splat/splat-generic.o splat-objs += @top_srcdir@/module/splat/splat-cred.o splat-objs += @top_srcdir@/module/splat/splat-zlib.o +splat-objs += @top_srcdir@/module/splat/splat-linux.o diff --git a/module/splat/splat-ctl.c b/module/splat/splat-ctl.c index 58e23051d3..dd1ece05bc 100644 --- a/module/splat/splat-ctl.c +++ b/module/splat/splat-ctl.c @@ -633,6 +633,7 @@ splat_init(void) SPLAT_SUBSYSTEM_INIT(generic); SPLAT_SUBSYSTEM_INIT(cred); SPLAT_SUBSYSTEM_INIT(zlib); + SPLAT_SUBSYSTEM_INIT(linux); dev = MKDEV(SPLAT_MAJOR, 0); if ((rc = register_chrdev_region(dev, SPLAT_MINORS, SPLAT_NAME))) @@ -681,6 +682,7 @@ splat_fini(void) cdev_del(&splat_cdev); unregister_chrdev_region(dev, SPLAT_MINORS); + SPLAT_SUBSYSTEM_FINI(linux); SPLAT_SUBSYSTEM_FINI(zlib); SPLAT_SUBSYSTEM_FINI(cred); SPLAT_SUBSYSTEM_FINI(generic); diff --git a/module/splat/splat-internal.h b/module/splat/splat-internal.h index 072abb8c74..501e42c32d 100644 --- a/module/splat/splat-internal.h +++ b/module/splat/splat-internal.h @@ -220,6 +220,7 @@ splat_subsystem_t *splat_list_init(void); splat_subsystem_t *splat_generic_init(void); splat_subsystem_t *splat_cred_init(void); splat_subsystem_t *splat_zlib_init(void); +splat_subsystem_t *splat_linux_init(void); void splat_condvar_fini(splat_subsystem_t *); void splat_kmem_fini(splat_subsystem_t *); @@ -236,6 +237,7 @@ void splat_list_fini(splat_subsystem_t *); void splat_generic_fini(splat_subsystem_t *); void splat_cred_fini(splat_subsystem_t *); void splat_zlib_fini(splat_subsystem_t *); +void splat_linux_fini(splat_subsystem_t *); int splat_condvar_id(void); int splat_kmem_id(void); @@ -252,5 +254,6 @@ int splat_list_id(void); int splat_generic_id(void); int splat_cred_id(void); int splat_zlib_id(void); +int splat_linux_id(void); #endif /* _SPLAT_INTERNAL_H */ diff --git a/module/splat/splat-linux.c b/module/splat/splat-linux.c new file mode 100644 index 0000000000..07126411ab --- /dev/null +++ b/module/splat/splat-linux.c @@ -0,0 +1,241 @@ +/*****************************************************************************\ + * Copyright (C) 2011 Lawrence Livermore National Security, LLC. + * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). + * Written by Brian Behlendorf . + * UCRL-CODE-235197 + * + * This file is part of the SPL, Solaris Porting Layer. + * For details, see . + * + * The SPL is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * The SPL is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with the SPL. If not, see . + ***************************************************************************** + * Solaris Porting LAyer Tests (SPLAT) Kernel Compatibility Tests. +\*****************************************************************************/ + +#include "splat-internal.h" + +#define SPLAT_LINUX_NAME "linux" +#define SPLAT_LINUX_DESC "Kernel Compatibility Tests" + +#define SPLAT_LINUX_TEST1_ID 0x1001 +#define SPLAT_LINUX_TEST1_NAME "shrink_dcache" +#define SPLAT_LINUX_TEST1_DESC "Shrink dcache test" + +#define SPLAT_LINUX_TEST2_ID 0x1002 +#define SPLAT_LINUX_TEST2_NAME "shrink_icache" +#define SPLAT_LINUX_TEST2_DESC "Shrink icache test" + +#define SPLAT_LINUX_TEST3_ID 0x1003 +#define SPLAT_LINUX_TEST3_NAME "shrinker" +#define SPLAT_LINUX_TEST3_DESC "Shrinker test" + + +/* + * Attempt to shrink the dcache memory. This is simply a functional + * to ensure we can correctly call the shrinker. We don't check that + * the cache actually decreased because we have no control over what + * else may be running on the system. This avoid false positives. + */ +static int +splat_linux_test1(struct file *file, void *arg) +{ + int remain_before; + int remain_after; + + remain_before = shrink_dcache_memory(0, GFP_KERNEL); + remain_after = shrink_dcache_memory(KMC_REAP_CHUNK, GFP_KERNEL); + + splat_vprint(file, SPLAT_LINUX_TEST1_NAME, + "Shrink dcache memory, remain %d -> %d\n", + remain_before, remain_after); + + return 0; +} + +/* + * Attempt to shrink the icache memory. This is simply a functional + * to ensure we can correctly call the shrinker. We don't check that + * the cache actually decreased because we have no control over what + * else may be running on the system. This avoid false positives. + */ +static int +splat_linux_test2(struct file *file, void *arg) +{ + int remain_before; + int remain_after; + + remain_before = shrink_icache_memory(0, GFP_KERNEL); + remain_after = shrink_icache_memory(KMC_REAP_CHUNK, GFP_KERNEL); + + splat_vprint(file, SPLAT_LINUX_TEST2_NAME, + "Shrink icache memory, remain %d -> %d\n", + remain_before, remain_after); + + return 0; +} + +SPL_SHRINKER_CALLBACK_FWD_DECLARE(splat_linux_shrinker_fn); +SPL_SHRINKER_DECLARE(splat_linux_shrinker, splat_linux_shrinker_fn, 1); +static unsigned long splat_linux_shrinker_size = 0; +static struct file *splat_linux_shrinker_file = NULL; + +static int +__splat_linux_shrinker_fn(struct shrinker *shrink, struct shrink_control *sc) +{ + static int failsafe = 0; + + if (sc->nr_to_scan) { + splat_linux_shrinker_size = splat_linux_shrinker_size - + MIN(sc->nr_to_scan, splat_linux_shrinker_size); + + splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME, + "Reclaimed %lu objects, size now %lu\n", + sc->nr_to_scan, splat_linux_shrinker_size); + } else { + splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME, + "Cache size is %lu\n", splat_linux_shrinker_size); + } + + /* Far more calls than expected abort drop_slab as a failsafe */ + if ((++failsafe % 1000) == 0) { + splat_vprint(splat_linux_shrinker_file, SPLAT_LINUX_TEST3_NAME, + "Far more calls than expected (%d), size now %lu\n", + failsafe, splat_linux_shrinker_size); + return -1; + } + + return (int)splat_linux_shrinker_size; +} + +SPL_SHRINKER_CALLBACK_WRAPPER(splat_linux_shrinker_fn); + +#define DROP_SLAB_CMD \ + "exec 0/proc/sys/vm/drop_caches " \ + " 2>/dev/null; " \ + "echo 2" + +static int +splat_linux_drop_slab(struct file *file) +{ + char *argv[] = { "/bin/sh", + "-c", + DROP_SLAB_CMD, + NULL }; + char *envp[] = { "HOME=/", + "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", + NULL }; + int rc; + + rc = call_usermodehelper(argv[0], argv, envp, 1); + if (rc) + splat_vprint(file, SPLAT_LINUX_TEST3_NAME, + "Failed user helper '%s %s %s', rc = %d\n", + argv[0], argv[1], argv[2], rc); + + return rc; +} + +/* + * Verify correct shrinker functionality by registering a shrinker + * with the required compatibility macros. We then use a simulated + * cache and force the systems caches to be dropped. The shrinker + * should be repeatedly called until it reports that the cache is + * empty. It is then cleanly unregistered and correct behavior is + * verified. There are now four slightly different supported shrinker + * API and this test ensures the compatibility code is correct. + */ +static int +splat_linux_test3(struct file *file, void *arg) +{ + int rc = -EINVAL; + + /* + * Globals used by the shrinker, it is not safe to run this + * test concurrently this is a safe assumption for SPLAT tests. + * Regardless we do some minimal checking a bail if concurrent + * use is detected. + */ + if (splat_linux_shrinker_size || splat_linux_shrinker_file) { + splat_vprint(file, SPLAT_LINUX_TEST3_NAME, + "Failed due to concurrent shrinker test, rc = %d\n", rc); + return (rc); + } + + splat_linux_shrinker_size = 1024; + splat_linux_shrinker_file = file; + + spl_register_shrinker(&splat_linux_shrinker); + rc = splat_linux_drop_slab(file); + if (rc) + goto out; + + if (splat_linux_shrinker_size != 0) { + splat_vprint(file, SPLAT_LINUX_TEST3_NAME, + "Failed cache was not shrunk to 0, size now %lu", + splat_linux_shrinker_size); + rc = -EDOM; + } +out: + spl_unregister_shrinker(&splat_linux_shrinker); + + splat_linux_shrinker_size = 0; + splat_linux_shrinker_file = NULL; + + return rc; +} + +splat_subsystem_t * +splat_linux_init(void) +{ + splat_subsystem_t *sub; + + sub = kmalloc(sizeof(*sub), GFP_KERNEL); + if (sub == NULL) + return NULL; + + memset(sub, 0, sizeof(*sub)); + strncpy(sub->desc.name, SPLAT_LINUX_NAME, SPLAT_NAME_SIZE); + strncpy(sub->desc.desc, SPLAT_LINUX_DESC, SPLAT_DESC_SIZE); + INIT_LIST_HEAD(&sub->subsystem_list); + INIT_LIST_HEAD(&sub->test_list); + spin_lock_init(&sub->test_lock); + sub->desc.id = SPLAT_SUBSYSTEM_LINUX; + + SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST1_NAME, SPLAT_LINUX_TEST1_DESC, + SPLAT_LINUX_TEST1_ID, splat_linux_test1); + SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST2_NAME, SPLAT_LINUX_TEST2_DESC, + SPLAT_LINUX_TEST2_ID, splat_linux_test2); + SPLAT_TEST_INIT(sub, SPLAT_LINUX_TEST3_NAME, SPLAT_LINUX_TEST3_DESC, + SPLAT_LINUX_TEST3_ID, splat_linux_test3); + + return sub; +} + +void +splat_linux_fini(splat_subsystem_t *sub) +{ + ASSERT(sub); + SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST3_ID); + SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST2_ID); + SPLAT_TEST_FINI(sub, SPLAT_LINUX_TEST1_ID); + + kfree(sub); +} + +int +splat_linux_id(void) { + return SPLAT_SUBSYSTEM_LINUX; +}