1193 lines
33 KiB
C
1193 lines
33 KiB
C
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
|
|
/*
|
|
*
|
|
* (C) COPYRIGHT 2014-2023 ARM Limited. All rights reserved.
|
|
*
|
|
* This program is free software and is provided to you under the terms of the
|
|
* GNU General Public License version 2 as published by the Free Software
|
|
* Foundation, and any use by you of this program is subject to the terms
|
|
* of such GNU license.
|
|
*
|
|
* 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, you can access it online at
|
|
* http://www.gnu.org/licenses/gpl-2.0.html.
|
|
*
|
|
*/
|
|
|
|
/* Kernel UTF suite, test and fixture management including user to kernel
|
|
* interaction
|
|
*/
|
|
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/version.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/sched.h>
|
|
#include <generated/autoconf.h>
|
|
|
|
#include <kutf/kutf_suite.h>
|
|
#include <kutf/kutf_resultset.h>
|
|
#include <kutf/kutf_utils.h>
|
|
#include <kutf/kutf_helpers.h>
|
|
#ifdef CONFIG_KPROBES
|
|
#include <kutf/kutf_kprobe.h>
|
|
#endif
|
|
|
|
/**
|
|
* struct kutf_application - Structure which represents kutf application
|
|
* @name: The name of this test application.
|
|
* @dir: The debugfs directory for this test
|
|
* @suite_list: List head to store all the suites which are part of this
|
|
* application
|
|
*/
|
|
struct kutf_application {
|
|
const char *name;
|
|
struct dentry *dir;
|
|
struct list_head suite_list;
|
|
};
|
|
|
|
/**
|
|
* struct kutf_test_function - Structure which represents kutf test function
|
|
* @suite: Back reference to the suite this test function
|
|
* belongs to
|
|
* @filters: Filters that apply to this test function
|
|
* @test_id: Test ID
|
|
* @execute: Function to run for this test
|
|
* @test_data: Static data for this test
|
|
* @node: List node for test_list
|
|
* @variant_list: List head to store all the variants which can run on
|
|
* this function
|
|
* @dir: debugfs directory for this test function
|
|
*/
|
|
struct kutf_test_function {
|
|
struct kutf_suite *suite;
|
|
unsigned int filters;
|
|
unsigned int test_id;
|
|
void (*execute)(struct kutf_context *context);
|
|
union kutf_callback_data test_data;
|
|
struct list_head node;
|
|
struct list_head variant_list;
|
|
struct dentry *dir;
|
|
};
|
|
|
|
/**
|
|
* struct kutf_test_fixture - Structure which holds information on the kutf
|
|
* test fixture
|
|
* @test_func: Test function this fixture belongs to
|
|
* @fixture_index: Index of this fixture
|
|
* @node: List node for variant_list
|
|
* @dir: debugfs directory for this test fixture
|
|
*/
|
|
struct kutf_test_fixture {
|
|
struct kutf_test_function *test_func;
|
|
unsigned int fixture_index;
|
|
struct list_head node;
|
|
struct dentry *dir;
|
|
};
|
|
|
|
static struct dentry *base_dir;
|
|
static struct workqueue_struct *kutf_workq;
|
|
|
|
/**
|
|
* struct kutf_convert_table - Structure which keeps test results
|
|
* @result_name: Status of the test result
|
|
* @result: Status value for a single test
|
|
*/
|
|
struct kutf_convert_table {
|
|
char result_name[50];
|
|
enum kutf_result_status result;
|
|
};
|
|
|
|
static const struct kutf_convert_table kutf_convert[] = {
|
|
#define ADD_UTF_RESULT(_name) \
|
|
{ \
|
|
#_name, _name, \
|
|
}
|
|
ADD_UTF_RESULT(KUTF_RESULT_BENCHMARK), ADD_UTF_RESULT(KUTF_RESULT_SKIP),
|
|
ADD_UTF_RESULT(KUTF_RESULT_UNKNOWN), ADD_UTF_RESULT(KUTF_RESULT_PASS),
|
|
ADD_UTF_RESULT(KUTF_RESULT_DEBUG), ADD_UTF_RESULT(KUTF_RESULT_INFO),
|
|
ADD_UTF_RESULT(KUTF_RESULT_WARN), ADD_UTF_RESULT(KUTF_RESULT_FAIL),
|
|
ADD_UTF_RESULT(KUTF_RESULT_FATAL), ADD_UTF_RESULT(KUTF_RESULT_ABORT),
|
|
};
|
|
|
|
#define UTF_CONVERT_SIZE (ARRAY_SIZE(kutf_convert))
|
|
|
|
/**
|
|
* kutf_create_context() - Create a test context in which a specific fixture
|
|
* of an application will be run and its results
|
|
* reported back to the user
|
|
* @test_fix: Test fixture to be run.
|
|
*
|
|
* The context's refcount will be initialized to 1.
|
|
*
|
|
* Return: Returns the created test context on success or NULL on failure
|
|
*/
|
|
static struct kutf_context *kutf_create_context(struct kutf_test_fixture *test_fix);
|
|
|
|
/**
|
|
* kutf_destroy_context() - Destroy a previously created test context, only
|
|
* once its refcount has become zero
|
|
* @kref: pointer to kref member within the context
|
|
*
|
|
* This should only be used via a kref_put() call on the context's kref member
|
|
*/
|
|
static void kutf_destroy_context(struct kref *kref);
|
|
|
|
/**
|
|
* kutf_context_get() - increment refcount on a context
|
|
* @context: the kutf context
|
|
*
|
|
* This must be used when the lifetime of the context might exceed that of the
|
|
* thread creating @context
|
|
*/
|
|
static void kutf_context_get(struct kutf_context *context);
|
|
|
|
/**
|
|
* kutf_context_put() - decrement refcount on a context, destroying it when it
|
|
* reached zero
|
|
* @context: the kutf context
|
|
*
|
|
* This must be used only after a corresponding kutf_context_get() call on
|
|
* @context, and the caller no longer needs access to @context.
|
|
*/
|
|
static void kutf_context_put(struct kutf_context *context);
|
|
|
|
/**
|
|
* kutf_set_result() - Set the test result against the specified test context
|
|
* @context: Test context
|
|
* @status: Result status
|
|
*/
|
|
static void kutf_set_result(struct kutf_context *context, enum kutf_result_status status);
|
|
|
|
/**
|
|
* kutf_set_expected_result() - Set the expected test result for the specified
|
|
* test context
|
|
* @context: Test context
|
|
* @expected_status: Expected result status
|
|
*/
|
|
static void kutf_set_expected_result(struct kutf_context *context,
|
|
enum kutf_result_status expected_status);
|
|
|
|
/**
|
|
* kutf_result_to_string() - Converts a KUTF result into a string
|
|
* @result_str: Output result string
|
|
* @result: Result status to convert
|
|
*
|
|
* Return: 1 if test result was successfully converted to string, 0 otherwise
|
|
*/
|
|
static int kutf_result_to_string(const char **result_str, enum kutf_result_status result)
|
|
{
|
|
size_t i;
|
|
int ret = 0;
|
|
|
|
for (i = 0; i < UTF_CONVERT_SIZE; i++) {
|
|
if (result == kutf_convert[i].result) {
|
|
*result_str = kutf_convert[i].result_name;
|
|
ret = 1;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* kutf_debugfs_const_string_read() - Simple debugfs read callback which
|
|
* returns a constant string
|
|
* @file: Opened file to read from
|
|
* @buf: User buffer to write the data into
|
|
* @len: Amount of data to read
|
|
* @ppos: Offset into file to read from
|
|
*
|
|
* Return: On success, the number of bytes read and offset @ppos advanced by
|
|
* this number; on error, negative value
|
|
*/
|
|
static ssize_t kutf_debugfs_const_string_read(struct file *file, char __user *buf, size_t len,
|
|
loff_t *ppos)
|
|
{
|
|
char *str = file->private_data;
|
|
|
|
return simple_read_from_buffer(buf, len, ppos, str, strlen(str));
|
|
}
|
|
|
|
static const struct file_operations kutf_debugfs_const_string_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = simple_open,
|
|
.read = kutf_debugfs_const_string_read,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
/**
|
|
* kutf_add_explicit_result() - Check if an explicit result needs to be added
|
|
* @context: KUTF test context
|
|
*/
|
|
static void kutf_add_explicit_result(struct kutf_context *context)
|
|
{
|
|
switch (context->expected_status) {
|
|
case KUTF_RESULT_UNKNOWN:
|
|
break;
|
|
|
|
case KUTF_RESULT_WARN:
|
|
if (context->status == KUTF_RESULT_WARN)
|
|
kutf_test_pass(context, "Pass (expected warn occurred)");
|
|
else if (context->status != KUTF_RESULT_SKIP)
|
|
kutf_test_fail(context, "Fail (expected warn missing)");
|
|
break;
|
|
|
|
case KUTF_RESULT_FAIL:
|
|
if (context->status == KUTF_RESULT_FAIL)
|
|
kutf_test_pass(context, "Pass (expected fail occurred)");
|
|
else if (context->status != KUTF_RESULT_SKIP) {
|
|
/* Force the expected status so the fail gets logged */
|
|
context->expected_status = KUTF_RESULT_PASS;
|
|
kutf_test_fail(context, "Fail (expected fail missing)");
|
|
}
|
|
break;
|
|
|
|
case KUTF_RESULT_FATAL:
|
|
if (context->status == KUTF_RESULT_FATAL)
|
|
kutf_test_pass(context, "Pass (expected fatal occurred)");
|
|
else if (context->status != KUTF_RESULT_SKIP)
|
|
kutf_test_fail(context, "Fail (expected fatal missing)");
|
|
break;
|
|
|
|
case KUTF_RESULT_ABORT:
|
|
if (context->status == KUTF_RESULT_ABORT)
|
|
kutf_test_pass(context, "Pass (expected abort occurred)");
|
|
else if (context->status != KUTF_RESULT_SKIP)
|
|
kutf_test_fail(context, "Fail (expected abort missing)");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void kutf_run_test(struct work_struct *data)
|
|
{
|
|
struct kutf_context *test_context = container_of(data, struct kutf_context, work);
|
|
struct kutf_suite *suite = test_context->suite;
|
|
struct kutf_test_function *test_func;
|
|
|
|
test_func = test_context->test_fix->test_func;
|
|
|
|
/*
|
|
* Call the create fixture function if required before the
|
|
* fixture is run
|
|
*/
|
|
if (suite->create_fixture)
|
|
test_context->fixture = suite->create_fixture(test_context);
|
|
|
|
/* Only run the test if the fixture was created (if required) */
|
|
if ((suite->create_fixture && test_context->fixture) || (!suite->create_fixture)) {
|
|
if (test_func->filters & KUTF_F_TEST_EXPECTED_FAILURE) {
|
|
kutf_test_fail(test_context,
|
|
"KUTF: Test marked as 'Expected Failure' not run.");
|
|
} else {
|
|
test_func->execute(test_context);
|
|
}
|
|
|
|
if (suite->remove_fixture)
|
|
suite->remove_fixture(test_context);
|
|
|
|
kutf_add_explicit_result(test_context);
|
|
}
|
|
|
|
kutf_add_result(test_context, KUTF_RESULT_TEST_FINISHED, NULL);
|
|
|
|
kutf_context_put(test_context);
|
|
}
|
|
|
|
/**
|
|
* kutf_debugfs_run_open() - Debugfs open callback for the "run" entry.
|
|
*
|
|
* @inode: inode of the opened file
|
|
* @file: Opened file to read from
|
|
*
|
|
* This function creates a KUTF context and queues it onto a workqueue to be
|
|
* run asynchronously. The resulting file descriptor can be used to communicate
|
|
* userdata to the test and to read back the results of the test execution.
|
|
*
|
|
* Return: 0 on success
|
|
*/
|
|
static int kutf_debugfs_run_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct kutf_test_fixture *test_fix = inode->i_private;
|
|
struct kutf_context *test_context;
|
|
int err = 0;
|
|
|
|
test_context = kutf_create_context(test_fix);
|
|
if (!test_context) {
|
|
err = -ENOMEM;
|
|
goto finish;
|
|
}
|
|
|
|
file->private_data = test_context;
|
|
|
|
/* This reference is release by the kutf_run_test */
|
|
kutf_context_get(test_context);
|
|
|
|
queue_work(kutf_workq, &test_context->work);
|
|
|
|
finish:
|
|
return err;
|
|
}
|
|
|
|
#define USERDATA_WARNING_MESSAGE "WARNING: This test requires userdata\n"
|
|
|
|
/**
|
|
* kutf_debugfs_run_read() - Debugfs read callback for the "run" entry.
|
|
* @file: Opened file to read from
|
|
* @buf: User buffer to write the data into
|
|
* @len: Amount of data to read
|
|
* @ppos: Offset into file to read from
|
|
*
|
|
* This function emits the results of the test, blocking until they are
|
|
* available.
|
|
*
|
|
* If the test involves user data then this will also return user data records
|
|
* to user space. If the test is waiting for user data then this function will
|
|
* output a message (to make the likes of 'cat' display it), followed by
|
|
* returning 0 to mark the end of file.
|
|
*
|
|
* Results will be emitted one at a time, once all the results have been read
|
|
* 0 will be returned to indicate there is no more data.
|
|
*
|
|
* Return: Number of bytes read.
|
|
*/
|
|
static ssize_t kutf_debugfs_run_read(struct file *file, char __user *buf, size_t len, loff_t *ppos)
|
|
{
|
|
struct kutf_context *test_context = file->private_data;
|
|
struct kutf_result *res;
|
|
unsigned long bytes_not_copied;
|
|
ssize_t bytes_copied = 0;
|
|
const char *kutf_str_ptr = NULL;
|
|
size_t kutf_str_len = 0;
|
|
size_t message_len = 0;
|
|
char separator = ':';
|
|
char terminator = '\n';
|
|
|
|
res = kutf_remove_result(test_context->result_set);
|
|
|
|
if (IS_ERR(res))
|
|
return PTR_ERR(res);
|
|
|
|
/*
|
|
* Handle 'fake' results - these results are converted to another
|
|
* form before being returned from the kernel
|
|
*/
|
|
switch (res->status) {
|
|
case KUTF_RESULT_TEST_FINISHED:
|
|
return 0;
|
|
case KUTF_RESULT_USERDATA_WAIT:
|
|
if (test_context->userdata.flags & KUTF_USERDATA_WARNING_OUTPUT) {
|
|
/*
|
|
* Warning message already output,
|
|
* signal end-of-file
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
message_len = sizeof(USERDATA_WARNING_MESSAGE) - 1;
|
|
if (message_len > len)
|
|
message_len = len;
|
|
|
|
bytes_not_copied = copy_to_user(buf, USERDATA_WARNING_MESSAGE, message_len);
|
|
if (bytes_not_copied != 0)
|
|
return -EFAULT;
|
|
test_context->userdata.flags |= KUTF_USERDATA_WARNING_OUTPUT;
|
|
return (ssize_t)message_len;
|
|
case KUTF_RESULT_USERDATA:
|
|
message_len = strlen(res->message);
|
|
if (message_len > len - 1) {
|
|
message_len = len - 1;
|
|
pr_warn("User data truncated, read not long enough\n");
|
|
}
|
|
bytes_not_copied = copy_to_user(buf, res->message, message_len);
|
|
if (bytes_not_copied != 0) {
|
|
pr_warn("Failed to copy data to user space buffer\n");
|
|
return -EFAULT;
|
|
}
|
|
/* Finally the terminator */
|
|
bytes_not_copied = copy_to_user(&buf[message_len], &terminator, 1);
|
|
if (bytes_not_copied != 0) {
|
|
pr_warn("Failed to copy data to user space buffer\n");
|
|
return -EFAULT;
|
|
}
|
|
return (ssize_t)message_len + 1;
|
|
default:
|
|
/* Fall through - this is a test result */
|
|
break;
|
|
}
|
|
|
|
/* Note: This code assumes a result is read completely */
|
|
kutf_result_to_string(&kutf_str_ptr, res->status);
|
|
if (kutf_str_ptr)
|
|
kutf_str_len = strlen(kutf_str_ptr);
|
|
|
|
if (res->message)
|
|
message_len = strlen(res->message);
|
|
|
|
if ((kutf_str_len + 1 + message_len + 1) > len) {
|
|
pr_err("Not enough space in user buffer for a single result");
|
|
return 0;
|
|
}
|
|
|
|
/* First copy the result string */
|
|
if (kutf_str_ptr) {
|
|
bytes_not_copied = copy_to_user(&buf[0], kutf_str_ptr, kutf_str_len);
|
|
bytes_copied += (ssize_t)(kutf_str_len - bytes_not_copied);
|
|
if (bytes_not_copied)
|
|
goto exit;
|
|
}
|
|
|
|
/* Then the separator */
|
|
bytes_not_copied = copy_to_user(&buf[bytes_copied], &separator, 1);
|
|
bytes_copied += (ssize_t)(1 - bytes_not_copied);
|
|
if (bytes_not_copied)
|
|
goto exit;
|
|
|
|
/* Finally Next copy the result string */
|
|
if (res->message) {
|
|
bytes_not_copied = copy_to_user(&buf[bytes_copied], res->message, message_len);
|
|
bytes_copied += (ssize_t)(message_len - bytes_not_copied);
|
|
if (bytes_not_copied)
|
|
goto exit;
|
|
}
|
|
|
|
/* Finally the terminator */
|
|
bytes_not_copied = copy_to_user(&buf[bytes_copied], &terminator, 1);
|
|
bytes_copied += (ssize_t)(1 - bytes_not_copied);
|
|
|
|
exit:
|
|
return bytes_copied;
|
|
}
|
|
|
|
/**
|
|
* kutf_debugfs_run_write() - Debugfs write callback for the "run" entry.
|
|
* @file: Opened file to write to
|
|
* @buf: User buffer to read the data from
|
|
* @len: Amount of data to write
|
|
* @ppos: Offset into file to write to
|
|
*
|
|
* This function allows user and kernel to exchange extra data necessary for
|
|
* the test fixture.
|
|
*
|
|
* The data is added to the first struct kutf_context running the fixture
|
|
*
|
|
* Return: Number of bytes written
|
|
*/
|
|
static ssize_t kutf_debugfs_run_write(struct file *file, const char __user *buf, size_t len,
|
|
loff_t *ppos)
|
|
{
|
|
int ret = 0;
|
|
struct kutf_context *test_context = file->private_data;
|
|
|
|
if (len > KUTF_MAX_LINE_LENGTH)
|
|
return -EINVAL;
|
|
|
|
ret = kutf_helper_input_enqueue(test_context, buf, len);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return (ssize_t)len;
|
|
}
|
|
|
|
/**
|
|
* kutf_debugfs_run_release() - Debugfs release callback for the "run" entry.
|
|
* @inode: File entry representation
|
|
* @file: A specific opening of the file
|
|
*
|
|
* Release any resources that were created during the opening of the file
|
|
*
|
|
* Note that resources may not be released immediately, that might only happen
|
|
* later when other users of the kutf_context release their refcount.
|
|
*
|
|
* Return: 0 on success
|
|
*/
|
|
static int kutf_debugfs_run_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct kutf_context *test_context = file->private_data;
|
|
|
|
kutf_helper_input_enqueue_end_of_data(test_context);
|
|
|
|
kutf_context_put(test_context);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations kutf_debugfs_run_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = kutf_debugfs_run_open,
|
|
.read = kutf_debugfs_run_read,
|
|
.write = kutf_debugfs_run_write,
|
|
.release = kutf_debugfs_run_release,
|
|
.llseek = default_llseek,
|
|
};
|
|
|
|
/**
|
|
* create_fixture_variant() - Creates a fixture variant for the specified
|
|
* test function and index and the debugfs entries
|
|
* that represent it.
|
|
* @test_func: Test function
|
|
* @fixture_index: Fixture index
|
|
*
|
|
* Return: 0 on success, negative value corresponding to error code in failure
|
|
*/
|
|
static int create_fixture_variant(struct kutf_test_function *test_func, unsigned int fixture_index)
|
|
{
|
|
struct kutf_test_fixture *test_fix;
|
|
char name[11]; /* Enough to print the MAX_UINT32 + the null terminator */
|
|
struct dentry *tmp;
|
|
int err;
|
|
|
|
test_fix = kmalloc(sizeof(*test_fix), GFP_KERNEL);
|
|
if (!test_fix) {
|
|
pr_err("Failed to create debugfs directory when adding fixture\n");
|
|
err = -ENOMEM;
|
|
goto fail_alloc;
|
|
}
|
|
|
|
test_fix->test_func = test_func;
|
|
test_fix->fixture_index = fixture_index;
|
|
|
|
snprintf(name, sizeof(name), "%d", fixture_index);
|
|
test_fix->dir = debugfs_create_dir(name, test_func->dir);
|
|
if (IS_ERR_OR_NULL(test_func->dir)) {
|
|
pr_err("Failed to create debugfs directory when adding fixture\n");
|
|
/* Might not be the right error, we don't get it passed back to us */
|
|
err = -EEXIST;
|
|
goto fail_dir;
|
|
}
|
|
|
|
tmp = debugfs_create_file("type", 0004, test_fix->dir, "fixture\n",
|
|
&kutf_debugfs_const_string_ops);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"type\" when adding fixture\n");
|
|
/* Might not be the right error, we don't get it passed back to us */
|
|
err = -EEXIST;
|
|
goto fail_file;
|
|
}
|
|
|
|
tmp = debugfs_create_file_unsafe("run", 0600, test_fix->dir, test_fix,
|
|
&kutf_debugfs_run_ops);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"run\" when adding fixture\n");
|
|
/* Might not be the right error, we don't get it passed back to us */
|
|
err = -EEXIST;
|
|
goto fail_file;
|
|
}
|
|
|
|
list_add(&test_fix->node, &test_func->variant_list);
|
|
return 0;
|
|
|
|
fail_file:
|
|
debugfs_remove_recursive(test_fix->dir);
|
|
fail_dir:
|
|
kfree(test_fix);
|
|
fail_alloc:
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* kutf_remove_test_variant() - Destroy a previously created fixture variant.
|
|
* @test_fix: Test fixture
|
|
*/
|
|
static void kutf_remove_test_variant(struct kutf_test_fixture *test_fix)
|
|
{
|
|
debugfs_remove_recursive(test_fix->dir);
|
|
kfree(test_fix);
|
|
}
|
|
|
|
#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE
|
|
/* Adapting to the upstream debugfs_create_x32() change */
|
|
static int ktufp_u32_get(void *data, u64 *val)
|
|
{
|
|
*val = *(u32 *)data;
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_DEBUGFS_ATTRIBUTE(kutfp_fops_x32_ro, ktufp_u32_get, NULL, "0x%08llx\n");
|
|
#endif
|
|
|
|
void kutf_add_test_with_filters_and_data(struct kutf_suite *suite, unsigned int id,
|
|
const char *name,
|
|
void (*execute)(struct kutf_context *context),
|
|
unsigned int filters, union kutf_callback_data test_data)
|
|
{
|
|
struct kutf_test_function *test_func;
|
|
struct dentry *tmp;
|
|
unsigned int i;
|
|
|
|
test_func = kmalloc(sizeof(*test_func), GFP_KERNEL);
|
|
if (!test_func) {
|
|
pr_err("Failed to allocate memory when adding test %s\n", name);
|
|
goto fail_alloc;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&test_func->variant_list);
|
|
|
|
test_func->dir = debugfs_create_dir(name, suite->dir);
|
|
if (IS_ERR_OR_NULL(test_func->dir)) {
|
|
pr_err("Failed to create debugfs directory when adding test %s\n", name);
|
|
goto fail_dir;
|
|
}
|
|
|
|
tmp = debugfs_create_file("type", 0004, test_func->dir, "test\n",
|
|
&kutf_debugfs_const_string_ops);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"type\" when adding test %s\n", name);
|
|
goto fail_file;
|
|
}
|
|
|
|
test_func->filters = filters;
|
|
#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE
|
|
tmp = debugfs_create_file_unsafe("filters", 0004, test_func->dir, &test_func->filters,
|
|
&kutfp_fops_x32_ro);
|
|
#else
|
|
tmp = debugfs_create_x32("filters", 0004, test_func->dir, &test_func->filters);
|
|
#endif
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"filters\" when adding test %s\n", name);
|
|
goto fail_file;
|
|
}
|
|
|
|
test_func->test_id = id;
|
|
#if KERNEL_VERSION(5, 5, 0) <= LINUX_VERSION_CODE
|
|
debugfs_create_u32("test_id", 0004, test_func->dir, &test_func->test_id);
|
|
#else
|
|
tmp = debugfs_create_u32("test_id", 0004, test_func->dir, &test_func->test_id);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"test_id\" when adding test %s\n", name);
|
|
goto fail_file;
|
|
}
|
|
#endif
|
|
|
|
for (i = 0; i < suite->fixture_variants; i++) {
|
|
if (create_fixture_variant(test_func, i)) {
|
|
pr_err("Failed to create fixture %d when adding test %s\n", i, name);
|
|
goto fail_file;
|
|
}
|
|
}
|
|
|
|
test_func->suite = suite;
|
|
test_func->execute = execute;
|
|
test_func->test_data = test_data;
|
|
|
|
list_add(&test_func->node, &suite->test_list);
|
|
return;
|
|
|
|
fail_file:
|
|
debugfs_remove_recursive(test_func->dir);
|
|
fail_dir:
|
|
kfree(test_func);
|
|
fail_alloc:
|
|
return;
|
|
}
|
|
EXPORT_SYMBOL(kutf_add_test_with_filters_and_data);
|
|
|
|
void kutf_add_test_with_filters(struct kutf_suite *suite, unsigned int id, const char *name,
|
|
void (*execute)(struct kutf_context *context), unsigned int filters)
|
|
{
|
|
union kutf_callback_data data;
|
|
|
|
data.ptr_value = NULL;
|
|
|
|
kutf_add_test_with_filters_and_data(suite, id, name, execute,
|
|
suite->suite_default_flags & filters, data);
|
|
}
|
|
EXPORT_SYMBOL(kutf_add_test_with_filters);
|
|
|
|
void kutf_add_test(struct kutf_suite *suite, unsigned int id, const char *name,
|
|
void (*execute)(struct kutf_context *context))
|
|
{
|
|
union kutf_callback_data data;
|
|
|
|
data.ptr_value = NULL;
|
|
|
|
kutf_add_test_with_filters_and_data(suite, id, name, execute, suite->suite_default_flags,
|
|
data);
|
|
}
|
|
EXPORT_SYMBOL(kutf_add_test);
|
|
|
|
/**
|
|
* kutf_remove_test() - Remove a previously added test function.
|
|
* @test_func: Test function
|
|
*/
|
|
static void kutf_remove_test(struct kutf_test_function *test_func)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &test_func->variant_list) {
|
|
struct kutf_test_fixture *test_fix;
|
|
|
|
test_fix = list_entry(pos, struct kutf_test_fixture, node);
|
|
kutf_remove_test_variant(test_fix);
|
|
}
|
|
|
|
list_del(&test_func->node);
|
|
debugfs_remove_recursive(test_func->dir);
|
|
kfree(test_func);
|
|
}
|
|
|
|
struct kutf_suite *
|
|
kutf_create_suite_with_filters_and_data(struct kutf_application *app, const char *name,
|
|
unsigned int fixture_count,
|
|
void *(*create_fixture)(struct kutf_context *context),
|
|
void (*remove_fixture)(struct kutf_context *context),
|
|
unsigned int filters, union kutf_callback_data suite_data)
|
|
{
|
|
struct kutf_suite *suite;
|
|
struct dentry *tmp;
|
|
|
|
suite = kmalloc(sizeof(*suite), GFP_KERNEL);
|
|
if (!suite) {
|
|
pr_err("Failed to allocate memory when creating suite %s\n", name);
|
|
goto fail_kmalloc;
|
|
}
|
|
|
|
suite->dir = debugfs_create_dir(name, app->dir);
|
|
if (IS_ERR_OR_NULL(suite->dir)) {
|
|
pr_err("Failed to create debugfs directory when adding test %s\n", name);
|
|
goto fail_debugfs;
|
|
}
|
|
|
|
tmp = debugfs_create_file("type", 0004, suite->dir, "suite\n",
|
|
&kutf_debugfs_const_string_ops);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"type\" when adding test %s\n", name);
|
|
goto fail_file;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&suite->test_list);
|
|
suite->app = app;
|
|
suite->name = name;
|
|
suite->fixture_variants = fixture_count;
|
|
suite->create_fixture = create_fixture;
|
|
suite->remove_fixture = remove_fixture;
|
|
suite->suite_default_flags = filters;
|
|
suite->suite_data = suite_data;
|
|
|
|
list_add(&suite->node, &app->suite_list);
|
|
|
|
return suite;
|
|
|
|
fail_file:
|
|
debugfs_remove_recursive(suite->dir);
|
|
fail_debugfs:
|
|
kfree(suite);
|
|
fail_kmalloc:
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(kutf_create_suite_with_filters_and_data);
|
|
|
|
struct kutf_suite *kutf_create_suite_with_filters(
|
|
struct kutf_application *app, const char *name, unsigned int fixture_count,
|
|
void *(*create_fixture)(struct kutf_context *context),
|
|
void (*remove_fixture)(struct kutf_context *context), unsigned int filters)
|
|
{
|
|
union kutf_callback_data data;
|
|
|
|
data.ptr_value = NULL;
|
|
return kutf_create_suite_with_filters_and_data(app, name, fixture_count, create_fixture,
|
|
remove_fixture, filters, data);
|
|
}
|
|
EXPORT_SYMBOL(kutf_create_suite_with_filters);
|
|
|
|
struct kutf_suite *kutf_create_suite(struct kutf_application *app, const char *name,
|
|
unsigned int fixture_count,
|
|
void *(*create_fixture)(struct kutf_context *context),
|
|
void (*remove_fixture)(struct kutf_context *context))
|
|
{
|
|
union kutf_callback_data data;
|
|
|
|
data.ptr_value = NULL;
|
|
return kutf_create_suite_with_filters_and_data(app, name, fixture_count, create_fixture,
|
|
remove_fixture, KUTF_F_TEST_GENERIC, data);
|
|
}
|
|
EXPORT_SYMBOL(kutf_create_suite);
|
|
|
|
/**
|
|
* kutf_destroy_suite() - Destroy a previously added test suite.
|
|
* @suite: Test suite
|
|
*/
|
|
static void kutf_destroy_suite(struct kutf_suite *suite)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &suite->test_list) {
|
|
struct kutf_test_function *test_func;
|
|
|
|
test_func = list_entry(pos, struct kutf_test_function, node);
|
|
kutf_remove_test(test_func);
|
|
}
|
|
|
|
list_del(&suite->node);
|
|
debugfs_remove_recursive(suite->dir);
|
|
kfree(suite);
|
|
}
|
|
|
|
struct kutf_application *kutf_create_application(const char *name)
|
|
{
|
|
struct kutf_application *app;
|
|
struct dentry *tmp;
|
|
|
|
app = kmalloc(sizeof(*app), GFP_KERNEL);
|
|
if (!app) {
|
|
pr_err("Failed to create allocate memory when creating application %s\n", name);
|
|
goto fail_kmalloc;
|
|
}
|
|
|
|
app->dir = debugfs_create_dir(name, base_dir);
|
|
if (IS_ERR_OR_NULL(app->dir)) {
|
|
pr_err("Failed to create debugfs direcotry when creating application %s\n", name);
|
|
goto fail_debugfs;
|
|
}
|
|
|
|
tmp = debugfs_create_file("type", 0004, app->dir, "application\n",
|
|
&kutf_debugfs_const_string_ops);
|
|
if (IS_ERR_OR_NULL(tmp)) {
|
|
pr_err("Failed to create debugfs file \"type\" when creating application %s\n",
|
|
name);
|
|
goto fail_file;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&app->suite_list);
|
|
app->name = name;
|
|
|
|
return app;
|
|
|
|
fail_file:
|
|
debugfs_remove_recursive(app->dir);
|
|
fail_debugfs:
|
|
kfree(app);
|
|
fail_kmalloc:
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(kutf_create_application);
|
|
|
|
void kutf_destroy_application(struct kutf_application *app)
|
|
{
|
|
struct list_head *pos;
|
|
struct list_head *tmp;
|
|
|
|
list_for_each_safe(pos, tmp, &app->suite_list) {
|
|
struct kutf_suite *suite;
|
|
|
|
suite = list_entry(pos, struct kutf_suite, node);
|
|
kutf_destroy_suite(suite);
|
|
}
|
|
|
|
debugfs_remove_recursive(app->dir);
|
|
kfree(app);
|
|
}
|
|
EXPORT_SYMBOL(kutf_destroy_application);
|
|
|
|
static struct kutf_context *kutf_create_context(struct kutf_test_fixture *test_fix)
|
|
{
|
|
struct kutf_context *new_context;
|
|
|
|
new_context = kmalloc(sizeof(*new_context), GFP_KERNEL);
|
|
if (!new_context) {
|
|
pr_err("Failed to allocate test context");
|
|
goto fail_context_alloc;
|
|
}
|
|
|
|
new_context->result_set = kutf_create_result_set();
|
|
if (!new_context->result_set) {
|
|
pr_err("Failed to create result set");
|
|
goto fail_result_set;
|
|
}
|
|
|
|
new_context->test_fix = test_fix;
|
|
/* Save the pointer to the suite as the callbacks will require it */
|
|
new_context->suite = test_fix->test_func->suite;
|
|
new_context->status = KUTF_RESULT_UNKNOWN;
|
|
new_context->expected_status = KUTF_RESULT_UNKNOWN;
|
|
|
|
kutf_mempool_init(&new_context->fixture_pool);
|
|
new_context->fixture = NULL;
|
|
new_context->fixture_index = test_fix->fixture_index;
|
|
new_context->fixture_name = NULL;
|
|
new_context->test_data = test_fix->test_func->test_data;
|
|
|
|
mutex_init(&new_context->output_sync);
|
|
|
|
new_context->userdata.flags = 0;
|
|
INIT_LIST_HEAD(&new_context->userdata.input_head);
|
|
init_waitqueue_head(&new_context->userdata.input_waitq);
|
|
|
|
INIT_WORK(&new_context->work, kutf_run_test);
|
|
|
|
kref_init(&new_context->kref);
|
|
|
|
return new_context;
|
|
|
|
fail_result_set:
|
|
kfree(new_context);
|
|
fail_context_alloc:
|
|
return NULL;
|
|
}
|
|
|
|
static void kutf_destroy_context(struct kref *kref)
|
|
{
|
|
struct kutf_context *context;
|
|
|
|
context = container_of(kref, struct kutf_context, kref);
|
|
kutf_destroy_result_set(context->result_set);
|
|
kutf_mempool_destroy(&context->fixture_pool);
|
|
kfree(context);
|
|
}
|
|
|
|
static void kutf_context_get(struct kutf_context *context)
|
|
{
|
|
kref_get(&context->kref);
|
|
}
|
|
|
|
static void kutf_context_put(struct kutf_context *context)
|
|
{
|
|
kref_put(&context->kref, kutf_destroy_context);
|
|
}
|
|
|
|
static void kutf_set_result(struct kutf_context *context, enum kutf_result_status status)
|
|
{
|
|
context->status = status;
|
|
}
|
|
|
|
static void kutf_set_expected_result(struct kutf_context *context,
|
|
enum kutf_result_status expected_status)
|
|
{
|
|
context->expected_status = expected_status;
|
|
}
|
|
|
|
/**
|
|
* kutf_test_log_result() - Log a result for the specified test context
|
|
* @context: Test context
|
|
* @message: Result string
|
|
* @new_status: Result status
|
|
*/
|
|
static void kutf_test_log_result(struct kutf_context *context, const char *message,
|
|
enum kutf_result_status new_status)
|
|
{
|
|
if (context->status < new_status)
|
|
context->status = new_status;
|
|
|
|
if (context->expected_status != new_status)
|
|
kutf_add_result(context, new_status, message);
|
|
}
|
|
|
|
void kutf_test_log_result_external(struct kutf_context *context, const char *message,
|
|
enum kutf_result_status new_status)
|
|
{
|
|
kutf_test_log_result(context, message, new_status);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_log_result_external);
|
|
|
|
void kutf_test_expect_abort(struct kutf_context *context)
|
|
{
|
|
kutf_set_expected_result(context, KUTF_RESULT_ABORT);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_expect_abort);
|
|
|
|
void kutf_test_expect_fatal(struct kutf_context *context)
|
|
{
|
|
kutf_set_expected_result(context, KUTF_RESULT_FATAL);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_expect_fatal);
|
|
|
|
void kutf_test_expect_fail(struct kutf_context *context)
|
|
{
|
|
kutf_set_expected_result(context, KUTF_RESULT_FAIL);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_expect_fail);
|
|
|
|
void kutf_test_expect_warn(struct kutf_context *context)
|
|
{
|
|
kutf_set_expected_result(context, KUTF_RESULT_WARN);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_expect_warn);
|
|
|
|
void kutf_test_expect_pass(struct kutf_context *context)
|
|
{
|
|
kutf_set_expected_result(context, KUTF_RESULT_PASS);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_expect_pass);
|
|
|
|
void kutf_test_skip(struct kutf_context *context)
|
|
{
|
|
kutf_set_result(context, KUTF_RESULT_SKIP);
|
|
kutf_set_expected_result(context, KUTF_RESULT_UNKNOWN);
|
|
|
|
kutf_test_log_result(context, "Test skipped", KUTF_RESULT_SKIP);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_skip);
|
|
|
|
void kutf_test_skip_msg(struct kutf_context *context, const char *message)
|
|
{
|
|
kutf_set_result(context, KUTF_RESULT_SKIP);
|
|
kutf_set_expected_result(context, KUTF_RESULT_UNKNOWN);
|
|
|
|
kutf_test_log_result(context,
|
|
kutf_dsprintf(&context->fixture_pool, "Test skipped: %s", message),
|
|
KUTF_RESULT_SKIP);
|
|
kutf_test_log_result(context, "!!!Test skipped!!!", KUTF_RESULT_SKIP);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_skip_msg);
|
|
|
|
void kutf_test_debug(struct kutf_context *context, char const *message)
|
|
{
|
|
kutf_test_log_result(context, message, KUTF_RESULT_DEBUG);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_debug);
|
|
|
|
void kutf_test_pass(struct kutf_context *context, char const *message)
|
|
{
|
|
static const char explicit_message[] = "(explicit pass)";
|
|
|
|
if (!message)
|
|
message = explicit_message;
|
|
|
|
kutf_test_log_result(context, message, KUTF_RESULT_PASS);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_pass);
|
|
|
|
void kutf_test_info(struct kutf_context *context, char const *message)
|
|
{
|
|
kutf_test_log_result(context, message, KUTF_RESULT_INFO);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_info);
|
|
|
|
__printf(2, 3) void kutf_test_info_msg(struct kutf_context *context, char const *msg, ...)
|
|
{
|
|
va_list args;
|
|
|
|
mutex_lock(&context->output_sync);
|
|
|
|
va_start(args, msg);
|
|
kutf_test_info(context, kutf_dsprintf(&context->fixture_pool, msg, args));
|
|
va_end(args);
|
|
|
|
mutex_unlock(&context->output_sync);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_info_msg);
|
|
|
|
void kutf_test_warn(struct kutf_context *context, char const *message)
|
|
{
|
|
kutf_test_log_result(context, message, KUTF_RESULT_WARN);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_warn);
|
|
|
|
void kutf_test_fail(struct kutf_context *context, char const *message)
|
|
{
|
|
kutf_test_log_result(context, message, KUTF_RESULT_FAIL);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_fail);
|
|
|
|
__printf(2, 3) void kutf_test_fail_msg(struct kutf_context *context, char const *msg, ...)
|
|
{
|
|
va_list args;
|
|
|
|
mutex_lock(&context->output_sync);
|
|
|
|
va_start(args, msg);
|
|
kutf_test_fail(context, kutf_dsprintf(&context->fixture_pool, msg, args));
|
|
va_end(args);
|
|
|
|
mutex_unlock(&context->output_sync);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_fail_msg);
|
|
|
|
void kutf_test_fatal(struct kutf_context *context, char const *message)
|
|
{
|
|
kutf_test_log_result(context, message, KUTF_RESULT_FATAL);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_fatal);
|
|
|
|
void kutf_test_abort(struct kutf_context *context)
|
|
{
|
|
kutf_test_log_result(context, "", KUTF_RESULT_ABORT);
|
|
}
|
|
EXPORT_SYMBOL(kutf_test_abort);
|
|
|
|
#if IS_ENABLED(CONFIG_DEBUG_FS)
|
|
|
|
/**
|
|
* init_kutf_core() - Module entry point.
|
|
* Create the base entry point in debugfs.
|
|
*
|
|
* Return: 0 on success, error code otherwise.
|
|
*/
|
|
static int __init init_kutf_core(void)
|
|
{
|
|
kutf_workq = alloc_workqueue("kutf workq", WQ_UNBOUND, 1);
|
|
if (!kutf_workq)
|
|
return -ENOMEM;
|
|
|
|
base_dir = debugfs_create_dir("kutf_tests", NULL);
|
|
if (IS_ERR_OR_NULL(base_dir)) {
|
|
destroy_workqueue(kutf_workq);
|
|
kutf_workq = NULL;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
#ifdef CONFIG_KPROBES
|
|
kutf_kprobe_init(base_dir);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* exit_kutf_core() - Module exit point.
|
|
*
|
|
* Remove the base entry point in debugfs.
|
|
*/
|
|
static void __exit exit_kutf_core(void)
|
|
{
|
|
#ifdef CONFIG_KPROBES
|
|
kutf_kprobe_exit();
|
|
#endif
|
|
debugfs_remove_recursive(base_dir);
|
|
|
|
if (kutf_workq)
|
|
destroy_workqueue(kutf_workq);
|
|
}
|
|
|
|
#else /* CONFIG_DEBUG_FS */
|
|
|
|
/**
|
|
* init_kutf_core - Module entry point
|
|
* Stub for when build against a kernel without debugfs support.
|
|
*
|
|
* Return: -ENODEV
|
|
*/
|
|
static int __init init_kutf_core(void)
|
|
{
|
|
pr_debug("KUTF requires a kernel with debug fs support");
|
|
|
|
return -ENODEV;
|
|
}
|
|
|
|
/**
|
|
* exit_kutf_core() - Module exit point.
|
|
*
|
|
* Stub for when build against a kernel without debugfs support
|
|
*/
|
|
static void __exit exit_kutf_core(void)
|
|
{
|
|
}
|
|
#endif /* CONFIG_DEBUG_FS */
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(init_kutf_core);
|
|
module_exit(exit_kutf_core);
|