/*
* kmod stress test driver
*
* Copyright (C) 2017 Luis R. Rodriguez <mcgrof@kernel.org>
*
* This program 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; or, when distributed separately from the Linux kernel or
* when incorporated into other software packages, subject to the following
* license:
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of copyleft-next (version 0.3.1 or later) as published
* at http://copyleft-next.org/.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/*
* This driver provides an interface to trigger and test the kernel's
* module loader through a series of configurations and a few triggers.
* To test this driver use the following script as root:
*
* tools/testing/selftests/kmod/kmod.sh --help
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/printk.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/device.h>
#define TEST_START_NUM_THREADS 50
#define TEST_START_DRIVER "test_module"
#define TEST_START_TEST_FS "xfs"
#define TEST_START_TEST_CASE TEST_KMOD_DRIVER
static bool force_init_test = false;
module_param(force_init_test, bool_enable_only, 0644);
MODULE_PARM_DESC(force_init_test,
"Force kicking a test immediately after driver loads");
/*
* For device allocation / registration
*/
static DEFINE_MUTEX(reg_dev_mutex);
static LIST_HEAD(reg_test_devs);
/*
* num_test_devs actually represents the *next* ID of the next
* device we will allow to create.
*/
static int num_test_devs;
/**
* enum kmod_test_case - linker table test case
*
* If you add a test case, please be sure to review if you need to se
* @need_mod_put for your tests case.
*
* @TEST_KMOD_DRIVER: stress tests request_module()
* @TEST_KMOD_FS_TYPE: stress tests get_fs_type()
*/
enum kmod_test_case {
__TEST_KMOD_INVALID = 0,
TEST_KMOD_DRIVER,
TEST_KMOD_FS_TYPE,
__TEST_KMOD_MAX,
};
struct test_config {
char *test_driver;
char *test_fs;
unsigned int num_threads;
enum kmod_test_case test_case;
int test_result;
};
struct kmod_test_device;
/**
* kmod_test_device_info - thread info
*
* @ret_sync: return value if request_module() is used, sync request for
* @TEST_KMOD_DRIVER
* @fs_sync: return value of get_fs_type() for @TEST_KMOD_FS_TYPE
* @thread_idx: thread ID
* @test_dev: test device test is being performed under
* @need_mod_put: Some tests (get_fs_type() is one) requires putting the module
* (module_put(fs_sync->owner)) when done, otherwise you will not be able
* to unload the respective modules and re-test. We use this to keep
* accounting of when we need this and to help out in case we need to
* error out and deal with module_put() on error.
*/
struct kmod_test_device_info {
int ret_sync;
struct file_system_type *fs_sync;
struct task_struct *task_sync;
unsigned int thread_idx;
struct kmod_test_device *test_dev;
bool need_mod_put;
};
/**
* kmod_test_device - test device to help test kmod
*
* @dev_idx: unique ID for test device
* @config: configuration for the test
* @misc_dev: we use a misc device under the hood
* @dev: pointer to misc_dev's own struct device
* @config_mutex: protects configuration of test
* @trigger_mutex: the test trigger can only be fired once at a time
* @thread_lock: protects @done count, and the @info per each thread
* @done: number of threads which have completed or failed
* @test_is_oom: when we run out of memory, use this to halt moving forward
* @kthreads_done: completion used to signal when all work is done
* @list: needed to be part of the reg_test_devs
* @info: array of info for each thread
*/
struct kmod_test_device {
int dev_idx;
struct test_config config;
struct miscdevice misc_dev;
struct device *dev;
struct mutex config_mutex;
struct mutex trigger_mutex;
struct mutex thread_mutex;
unsigned int done;
bool test_is_oom;
struct completion kthreads_done;
struct list_head list;
struct kmod_test_device_info *info;
};
static const char *test_case_str(enum kmod_test_case test_case)
{
switch (test_case) {
case TEST_KMOD_DRIVER:
return "TEST_KMOD_DRIVER";
case TEST_KMOD_FS_TYPE:
return "TEST_KMOD_FS_TYPE";
default:
return "invalid";
}
}