Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2014, 2017-2022 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>

/**
 * 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)
{
	int 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)) {
		/* Run this fixture */
		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 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 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 += 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 += 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 += 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 += 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 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,
					    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_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;

	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_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);

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);

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;
	}

	return 0;
}

/**
 * exit_kutf_core() - Module exit point.
 *
 * Remove the base entry point in debugfs.
 */
static void __exit exit_kutf_core(void)
{
	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);