diff options
Diffstat (limited to 'mali_kbase/tests/kutf/kutf_suite.c')
-rw-r--r-- | mali_kbase/tests/kutf/kutf_suite.c | 596 |
1 files changed, 209 insertions, 387 deletions
diff --git a/mali_kbase/tests/kutf/kutf_suite.c b/mali_kbase/tests/kutf/kutf_suite.c index ad30cc8..4968f24 100644 --- a/mali_kbase/tests/kutf/kutf_suite.c +++ b/mali_kbase/tests/kutf/kutf_suite.c @@ -27,12 +27,14 @@ #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> #if defined(CONFIG_DEBUG_FS) @@ -61,8 +63,6 @@ struct kutf_application { * @variant_list: List head to store all the variants which can run on * this function * @dir: debugfs directory for this test function - * @userdata_ops: Callbacks to use for sending and receiving data to - * userspace. */ struct kutf_test_function { struct kutf_suite *suite; @@ -73,7 +73,6 @@ struct kutf_test_function { struct list_head node; struct list_head variant_list; struct dentry *dir; - struct kutf_userdata_ops userdata_ops; }; /** @@ -83,17 +82,16 @@ struct kutf_test_function { * @fixture_index: Index of this fixture * @node: List node for variant_list * @dir: debugfs directory for this test fixture - * @nr_running: Current count of user-clients running this fixture */ struct kutf_test_fixture { struct kutf_test_function *test_func; unsigned int fixture_index; struct list_head node; struct dentry *dir; - atomic_t nr_running; }; -struct dentry *base_dir; +static struct dentry *base_dir; +static struct workqueue_struct *kutf_workq; /** * struct kutf_convert_table - Structure which keeps test results @@ -252,263 +250,6 @@ static const struct file_operations kutf_debugfs_const_string_ops = { }; /** - * kutf_debugfs_data_open() Debugfs open callback for the "data" entry. - * @inode: inode of the opened file - * @file: Opened file to read from - * - * This function notifies the userdata callbacks that the userdata file has - * been opened, for tracking purposes. - * - * It is called on both the context's userdata_consumer_priv and - * userdata_producer_priv. - * - * This takes a refcount on the kutf_context - * - * Return: 0 on success - */ -static int kutf_debugfs_data_open(struct inode *inode, struct file *file) -{ - struct kutf_context *test_context = inode->i_private; - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - int err; - - simple_open(inode, file); - - /* This is not an error */ - if (!test_func->userdata_ops.open) - goto out_no_ops; - - /* This is safe here - the 'data' file is only openable whilst the - * initial refcount is still present, and the initial refcount is only - * dropped strictly after the 'data' file is removed */ - kutf_context_get(test_context); - - if (test_context->userdata_consumer_priv) { - err = test_func->userdata_ops.open(test_context->userdata_consumer_priv); - if (err) - goto out_consumer_fail; - } - - if (test_context->userdata_producer_priv) { - err = test_func->userdata_ops.open(test_context->userdata_producer_priv); - if (err) - goto out_producer_fail; - } - -out_no_ops: - return 0; - -out_producer_fail: - if (test_func->userdata_ops.release && test_context->userdata_consumer_priv) - test_func->userdata_ops.release(test_context->userdata_consumer_priv); -out_consumer_fail: - kutf_context_put(test_context); - - return err; -} - - -/** - * kutf_debugfs_data_read() Debugfs read callback for the "data" 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 allows user and kernel to exchange extra data necessary for - * the test fixture. - * - * The data is read from the first struct kutf_context running the fixture - * - * Return: Number of bytes read - */ -static ssize_t kutf_debugfs_data_read(struct file *file, char __user *buf, - size_t len, loff_t *ppos) -{ - struct kutf_context *test_context = file->private_data; - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - ssize_t (*producer)(void *private, char __user *userbuf, - size_t userbuf_len, loff_t *ppos); - ssize_t count; - - producer = test_func->userdata_ops.producer; - /* Can only read if there's a producer callback */ - if (!producer) - return -ENODEV; - - count = producer(test_context->userdata_producer_priv, buf, len, ppos); - - return count; -} - -/** - * kutf_debugfs_data_write() Debugfs write callback for the "data" 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_data_write(struct file *file, - const char __user *buf, size_t len, loff_t *ppos) -{ - struct kutf_context *test_context = file->private_data; - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - ssize_t (*consumer)(void *private, const char __user *userbuf, - size_t userbuf_len, loff_t *ppos); - ssize_t count; - - consumer = test_func->userdata_ops.consumer; - /* Can only write if there's a consumer callback */ - if (!consumer) - return -ENODEV; - - count = consumer(test_context->userdata_consumer_priv, buf, len, ppos); - - return count; -} - - -/** - * kutf_debugfs_data_release() - Debugfs release callback for the "data" entry. - * @inode: File entry representation - * @file: A specific opening of the file - * - * This function notifies the userdata callbacks that the userdata file has - * been closed, for tracking purposes. - * - * It is called on both the context's userdata_consumer_priv and - * userdata_producer_priv. - * - * It also drops the refcount on the kutf_context that was taken during - * kutf_debugfs_data_open() - */ -static int kutf_debugfs_data_release(struct inode *inode, struct file *file) -{ - struct kutf_context *test_context = file->private_data; - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - - if (!test_func->userdata_ops.release) - return 0; - - if (test_context->userdata_consumer_priv) - test_func->userdata_ops.release(test_context->userdata_consumer_priv); - if (test_context->userdata_producer_priv) - test_func->userdata_ops.release(test_context->userdata_producer_priv); - - kutf_context_put(test_context); - - return 0; -} - - -static const struct file_operations kutf_debugfs_data_ops = { - .owner = THIS_MODULE, - .open = kutf_debugfs_data_open, - .read = kutf_debugfs_data_read, - .write = kutf_debugfs_data_write, - .release = kutf_debugfs_data_release, - .llseek = default_llseek, -}; - -/** - * userdata_init() - Initialize userspace data exchange for a test, if - * specified by that test - * @test_context: Test context - * - * Note that this allows new refcounts to be made on test_context by userspace - * threads opening the 'data' file. - * - * Return: 0 on success, negative value corresponding to error code in failure - * and kutf result will be set appropriately to indicate the error - */ -static int userdata_init(struct kutf_context *test_context) -{ - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - int err = 0; - struct dentry *userdata_dentry; - - /* Valid to have neither a producer or consumer, which is the case for - * tests not requiring usersdata */ - if ((!test_func->userdata_ops.consumer) && (!test_func->userdata_ops.producer)) - return err; - - if (test_func->userdata_ops.consumer && !test_context->userdata_consumer_priv) { - kutf_test_fatal(test_context, - "incorrect test setup - userdata consumer provided without private data"); - return -EFAULT; - } - - if (test_func->userdata_ops.producer && !test_context->userdata_producer_priv) { - kutf_test_fatal(test_context, - "incorrect test setup - userdata producer provided without private data"); - return -EFAULT; - } - - userdata_dentry = debugfs_create_file("data", S_IROTH, test_fix->dir, - test_context, &kutf_debugfs_data_ops); - - if (!userdata_dentry) { - pr_err("Failed to create debugfs file \"data\" when running fixture\n"); - /* Not using Fatal (which stops other tests running), - * nor Abort (which indicates teardown should not be done) */ - kutf_test_fail(test_context, - "failed to create 'data' file for userside data exchange"); - - /* Error code is discarded by caller, but consistent with other - * debugfs_create_file failures */ - err = -EEXIST; - } else { - test_context->userdata_dentry = userdata_dentry; - } - - - return err; -} - -/** - * userdata_term() - Terminate userspace data exchange for a test, if specified - * by that test - * @test_context: Test context - * - * Note This also prevents new refcounts being made on @test_context by userspace - * threads opening the 'data' file for this test. Any existing open file descriptors - * to the 'data' file will still be safe to use by userspace. - */ -static void userdata_term(struct kutf_context *test_context) -{ - struct kutf_test_fixture *test_fix = test_context->test_fix; - struct kutf_test_function *test_func = test_fix->test_func; - void (*notify_ended)(void *priv) = test_func->userdata_ops.notify_ended; - - /* debugfs_remove() is safe when parameter is error or NULL */ - debugfs_remove(test_context->userdata_dentry); - - /* debugfs_remove() doesn't kill any currently open file descriptors on - * this file, and such fds are still safe to use providing test_context - * is properly refcounted */ - - if (notify_ended) { - if (test_context->userdata_consumer_priv) - notify_ended(test_context->userdata_consumer_priv); - if (test_context->userdata_producer_priv) - notify_ended(test_context->userdata_producer_priv); - } - -} - -/** * kutf_add_explicit_result() - Check if an explicit result needs to be added * @context: KUTF test context */ @@ -563,75 +304,75 @@ static void kutf_add_explicit_result(struct kutf_context *context) } } +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 retrieves the test fixture data that is associated with the - * opened file and works back to get the test, suite and application so - * it can then run the test that is associated with the file entry. + * 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_test_function *test_func = test_fix->test_func; - struct kutf_suite *suite = test_func->suite; struct kutf_context *test_context; int err = 0; - /* For the moment, only one user-client should be attempting to run - * this at a time. This simplifies how we lookup the kutf_context when - * using the 'data' file. - * Removing this restriction would require a rewrite of the mechanism - * of the 'data' file to pass data in, perhaps 'data' created here and - * based upon userspace thread's pid */ - if (atomic_inc_return(&test_fix->nr_running) != 1) { - err = -EBUSY; - goto finish; - } - test_context = kutf_create_context(test_fix); if (!test_context) { - err = -ENODEV; + err = -ENOMEM; goto finish; } file->private_data = test_context; - /* - * 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)) { - int late_err; - /* Setup any userdata exchange */ - late_err = userdata_init(test_context); - - if (!late_err) - /* Run this fixture */ - test_func->execute(test_context); - - userdata_term(test_context); - - if (suite->remove_fixture) - suite->remove_fixture(test_context); + /* This reference is release by the kutf_run_test */ + kutf_context_get(test_context); - kutf_add_explicit_result(test_context); - } + queue_work(kutf_workq, &test_context->work); finish: - atomic_dec(&test_fix->nr_running); 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 @@ -639,8 +380,14 @@ finish: * @len: Amount of data to read * @ppos: Offset into file to read from * - * This function emits the results which where logged during the opening of - * the file kutf_debugfs_run_open. + * 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. * @@ -653,68 +400,153 @@ static ssize_t kutf_debugfs_run_read(struct file *file, char __user *buf, struct kutf_result *res; unsigned long bytes_not_copied; ssize_t bytes_copied = 0; + char *kutf_str_ptr = NULL; + size_t kutf_str_len = 0; + size_t message_len = 0; + char separator = ':'; + char terminator = '\n'; - /* Note: This code assumes a result is read completely */ res = kutf_remove_result(test_context->result_set); - if (res) { - char *kutf_str_ptr = NULL; - unsigned int kutf_str_len = 0; - unsigned int message_len = 0; - char separator = ':'; - char terminator = '\n'; - - 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"); + + 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; } - /* 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; + 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; + } - /* Then the separator */ - bytes_not_copied = copy_to_user(&buf[bytes_copied], - &separator, 1); - bytes_copied += 1 - bytes_not_copied; + /* 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; + } - /* 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; - } + /* 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 the terminator */ + /* Finally Next copy the result string */ + if (res->message) { bytes_not_copied = copy_to_user(&buf[bytes_copied], - &terminator, 1); - bytes_copied += 1 - bytes_not_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 where created during the 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. @@ -725,6 +557,8 @@ 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; } @@ -733,6 +567,7 @@ 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, }; @@ -763,7 +598,6 @@ static int create_fixture_variant(struct kutf_test_function *test_func, test_fix->test_func = test_func; test_fix->fixture_index = fixture_index; - atomic_set(&test_fix->nr_running, 0); snprintf(name, sizeof(name), "%d", fixture_index); test_fix->dir = debugfs_create_dir(name, test_func->dir); @@ -783,8 +617,14 @@ static int create_fixture_variant(struct kutf_test_function *test_func, goto fail_file; } - tmp = debugfs_create_file("run", S_IROTH, test_fix->dir, test_fix, - &kutf_debugfs_run_ops); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0) + tmp = debugfs_create_file_unsafe( +#else + tmp = debugfs_create_file( +#endif + "run", 0600, test_fix->dir, + test_fix, + &kutf_debugfs_run_ops); if (!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 */ @@ -813,14 +653,13 @@ static void kutf_remove_test_variant(struct kutf_test_fixture *test_fix) kfree(test_fix); } -void kutf_add_test_with_filters_data_and_userdata( +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_userdata_ops *userdata_ops) + union kutf_callback_data test_data) { struct kutf_test_function *test_func; struct dentry *tmp; @@ -873,7 +712,6 @@ void kutf_add_test_with_filters_data_and_userdata( test_func->suite = suite; test_func->execute = execute; test_func->test_data = test_data; - memcpy(&test_func->userdata_ops, userdata_ops, sizeof(*userdata_ops)); list_add(&test_func->node, &suite->test_list); return; @@ -885,27 +723,6 @@ fail_dir: fail_alloc: return; } -EXPORT_SYMBOL(kutf_add_test_with_filters_data_and_userdata); - -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_userdata_ops userdata_ops = { - .open = NULL, - .release = NULL, - .consumer = NULL, - .producer = NULL, - }; - - kutf_add_test_with_filters_data_and_userdata(suite, id, name, execute, - filters, test_data, &userdata_ops); -} - EXPORT_SYMBOL(kutf_add_test_with_filters_and_data); void kutf_add_test_with_filters( @@ -1150,7 +967,7 @@ static struct kutf_context *kutf_create_context( new_context->result_set = kutf_create_result_set(); if (!new_context->result_set) { - pr_err("Failed to create resultset"); + pr_err("Failed to create result set"); goto fail_result_set; } @@ -1165,9 +982,12 @@ static struct kutf_context *kutf_create_context( 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_consumer_priv = NULL; - new_context->userdata_producer_priv = NULL; - new_context->userdata_dentry = NULL; + + 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); @@ -1227,8 +1047,7 @@ static void kutf_test_log_result( context->status = new_status; if (context->expected_status != new_status) - kutf_add_result(&context->fixture_pool, context->result_set, - new_status, message); + kutf_add_result(context, new_status, message); } void kutf_test_log_result_external( @@ -1344,18 +1163,18 @@ EXPORT_SYMBOL(kutf_test_abort); */ static int __init init_kutf_core(void) { - int ret; + kutf_workq = alloc_workqueue("kutf workq", WQ_UNBOUND, 1); + if (!kutf_workq) + return -ENOMEM; base_dir = debugfs_create_dir("kutf_tests", NULL); if (!base_dir) { - ret = -ENODEV; - goto exit_dir; + destroy_workqueue(kutf_workq); + kutf_workq = NULL; + return -ENOMEM; } return 0; - -exit_dir: - return ret; } /** @@ -1366,6 +1185,9 @@ exit_dir: static void __exit exit_kutf_core(void) { debugfs_remove_recursive(base_dir); + + if (kutf_workq) + destroy_workqueue(kutf_workq); } #else /* defined(CONFIG_DEBUG_FS) */ |