/* * Copyright (c) 2019 AVM GmbH . * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("Paul Hüber"); MODULE_DESCRIPTION("A minimalist test harness for kernel modules"); struct ktd_test { const char *name; ktd_ret_t (*fn)(void *arg); void *arg; }; struct ktd_suite { struct ktd_test tests[50]; unsigned int num_tests; struct dentry *dbgfs_node; }; static struct dentry *ktd_root_node; struct ktd_suite *ktd_suite; static int ktd_open(struct inode *inode, struct file *file) { struct ktd_test *test; /* restore private data from debugfs_create_file */ test = inode->i_private; /* store result string for successive read calls */ file->private_data = (void *)test->fn(test->arg) ?: KTD_PASSED_STR; return nonseekable_open(inode, file); } static ssize_t ktd_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { const char *content; /* restore result string determined during 'open' */ content = file->private_data; return simple_read_from_buffer(user_buf, count, ppos, content, strlen(content) + 1); } static const struct file_operations ktd_fops = { .read = ktd_read, .open = ktd_open, .llseek = default_llseek, }; struct ktd_suite *ktd_suite_create(const char *name) { struct ktd_suite *suite; if (!ktd_root_node) return NULL; suite = kzalloc(sizeof(*suite), GFP_ATOMIC); if (!suite) return NULL; suite->dbgfs_node = debugfs_create_dir(name, ktd_root_node); if (!suite->dbgfs_node) { struct qstr qname; qname.name = name; qname.len = strlen(name); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0) qname.hash = full_name_hash(ktd_root_node, qname.name, qname.len); #else qname.hash = full_name_hash(qname.name, qname.len); #endif suite->dbgfs_node = d_lookup(ktd_root_node, &qname); pr_debug("debugfs node exists, d_lookup returned %pK\n", suite->dbgfs_node); } return suite; } EXPORT_SYMBOL(ktd_suite_create); void ktd_suite_destroy(struct ktd_suite *suite) { if (!suite) return; debugfs_remove_recursive(suite->dbgfs_node); kfree(suite); } EXPORT_SYMBOL(ktd_suite_destroy); int ktd_register(struct ktd_suite *suite, const char *name, ktd_ret_t(fn)(void *), void *arg) { struct ktd_test *test; if (!suite || !name || !fn) return -EINVAL; if (suite->num_tests >= ARRAY_SIZE(suite->tests)) return -ENOMEM; test = &suite->tests[suite->num_tests++]; test->name = name; test->fn = fn; test->arg = arg; debugfs_create_file(name, 0400, suite->dbgfs_node, test, &ktd_fops); return 0; } EXPORT_SYMBOL(ktd_register); struct fn_thread_arg { ktd_ret_t (*fn)(unsigned int cpuid); struct completion *compl; ktd_ret_t result; }; static int fn_thread(void *arg) { struct fn_thread_arg *targ; targ = arg; targ->result = targ->fn(smp_processor_id()); complete(targ->compl); while (!kthread_should_stop()) schedule(); return 0; } static ktd_ret_t fn_run_threaded(void *arg) { struct task_struct *threads[NR_CPUS]; struct fn_thread_arg args[ARRAY_SIZE(threads)]; struct completion compl; int i; /* Do some concurrent operations to check soundness of the atomic * accesses. */ init_completion(&compl); for (i = 0; i < ARRAY_SIZE(threads); i++) { args[i].fn = arg; args[i].compl = &compl; threads[i] = kthread_create(fn_thread, &args[i], "ktestdrive_%pK(%d)", arg, i); KTD_EXPECT(!IS_ERR(threads[i])); kthread_bind(threads[i], i); wake_up_process(threads[i]); } /* Wait for threads to finish before timeout */ for (i = 0; i < ARRAY_SIZE(threads); i++) KTD_EXPECT(wait_for_completion_interruptible_timeout( &compl, 10 * CONFIG_HZ) > 0); /* Check results and shut down threads. */ for (i = 0; i < ARRAY_SIZE(threads); i++) { if (args[i].result != KTD_PASSED) return args[i].result; kthread_stop(threads[i]); } return KTD_PASSED; } int ktd_register_concurrent(struct ktd_suite *suite, const char *name, ktd_ret_t(fn)(unsigned int cpuid)) { return ktd_register(suite, name, fn_run_threaded, fn); } EXPORT_SYMBOL(ktd_register_concurrent); static ktd_ret_t test_data_eq_null(void *data) { KTD_EXPECT(!data); return KTD_PASSED; } static void ktd_suite_create_test_async(void *data, async_cookie_t cookie) { ktd_suite_destroy(data); } static ktd_ret_t ktd_suite_create_test(void *data) { struct ktd_suite *s1, *s2; s1 = ktd_suite_create(__func__); KTD_EXPECT(s1); s2 = ktd_suite_create(__func__); KTD_EXPECT(s2); KTD_EXPECT(s1->dbgfs_node == s2->dbgfs_node); /* * Escape from the critical reader section of debugfs to remove the * entries without deadlocking. */ async_schedule(ktd_suite_create_test_async, s1); async_schedule(ktd_suite_create_test_async, s2); return KTD_PASSED; } int __init ktd_init(void) { int rv; ktd_root_node = debugfs_create_dir(THIS_MODULE->name, NULL); /* self-tests */ ktd_suite = ktd_suite_create(THIS_MODULE->name); rv = ktd_register(ktd_suite, "expect_pass", test_data_eq_null, NULL); if (rv) return rv; rv = ktd_register(ktd_suite, "expect_fail", test_data_eq_null, (void *)1); if (rv) return rv; rv = ktd_register(ktd_suite, "recreate_suite", ktd_suite_create_test, NULL); if (rv) return rv; return 0; } void __exit ktd_exit(void) { ktd_suite_destroy(ktd_suite); ktd_suite = NULL; debugfs_remove_recursive(ktd_root_node); } module_init(ktd_init); module_exit(ktd_exit);