/*****************************************************************************\ * 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; }