diff options
author | Sidath Senanayake <sidaths@google.com> | 2017-07-11 16:57:40 +0200 |
---|---|---|
committer | Sidath Senanayake <sidaths@google.com> | 2017-07-11 16:57:40 +0200 |
commit | ea23e535ae857c92d45cb11bdd5dba7c27579726 (patch) | |
tree | e1bcda85e529f9be3f02202b81fb3e8f6ab73129 /mali_kbase/tests | |
parent | 6f5ab3baed824941f168ab133469f997d4450146 (diff) | |
download | gpu-ea23e535ae857c92d45cb11bdd5dba7c27579726.tar.gz |
Mali Bifrost DDK r7p0 KMD
Provenance:
cbfad67c8 (collaborate/EAC/b_r7p0)
BX304L01B-BU-00000-r7p0-01rel0
BX304L06A-BU-00000-r7p0-01rel0
BX304X07X-BU-00000-r7p0-01rel0
Signed-off-by: Sidath Senanayake <sidaths@google.com>
Change-Id: Icdf8b47a48b829cc228f4df3035f7b539da58104
Diffstat (limited to 'mali_kbase/tests')
-rw-r--r-- | mali_kbase/tests/include/kutf/kutf_helpers.h | 216 | ||||
-rw-r--r-- | mali_kbase/tests/include/kutf/kutf_helpers_user.h | 179 | ||||
-rw-r--r-- | mali_kbase/tests/include/kutf/kutf_mem.h | 3 | ||||
-rw-r--r-- | mali_kbase/tests/include/kutf/kutf_suite.h | 62 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/Kbuild | 2 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_helpers.c | 768 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_helpers_user.c | 462 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_mem.c | 10 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_suite.c | 381 | ||||
-rw-r--r-- | mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c | 12 | ||||
-rw-r--r-- | mali_kbase/tests/sconscript | 1 |
11 files changed, 2081 insertions, 15 deletions
diff --git a/mali_kbase/tests/include/kutf/kutf_helpers.h b/mali_kbase/tests/include/kutf/kutf_helpers.h new file mode 100644 index 0000000..3f1dfc2 --- /dev/null +++ b/mali_kbase/tests/include/kutf/kutf_helpers.h @@ -0,0 +1,216 @@ +/* + * + * (C) COPYRIGHT 2017 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 licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +#ifndef _KERNEL_UTF_HELPERS_H_ +#define _KERNEL_UTF_HELPERS_H_ + +/* kutf_helpers.h + * Test helper functions for the kernel UTF test infrastructure. + * + * This collection of helper functions are provided as 'stock' implementation + * helpers for certain features of kutf. Tests can implement common/boilerplate + * functionality using these, whilst still providing them the option of + * implementing completely custom functions themselves to use those kutf + * features. + */ + +#include <kutf/kutf_suite.h> +#include <kutf/kutf_mem.h> +#include <linux/wait.h> + +/** + * enum kutf_helper_textbuf_flag - flags for textbufs + * @KUTF_HELPER_TEXTBUF_FLAG_DYING: Test is dying, textbuf should not allow + * writes, nor block on empty. + */ +enum kutf_helper_textbuf_flag { + KUTF_HELPER_TEXTBUF_FLAG_DYING = (1u << 0), +}; + +/** + * struct kutf_helper_textbuf_line - Structure representing a line of text + * + * The string itself is stored immediately after this. + * + * @node: List node for the textbuf's textbuf_list + * @str_size: Length of the string buffer, including the \0 terminator + * @str: 'Flexible array' for the string representing the line + */ +struct kutf_helper_textbuf_line { + struct list_head node; + int str_size; + char str[]; +}; + +/** + * struct kutf_helper_textbuf - Structure to representing sequential lines of + * text + * @lock: mutex to hold whilst accessing the structure + * @nr_user_clients: Number of userspace clients connected via an open() + * call + * @mempool: mempool for allocating lines + * @scratchpad: scratch area for receiving text of size max_line_size + * @used_bytes: number of valid bytes in the scratchpad + * @prev_pos: Previous position userspace has accessed + * @prev_line_pos: Previous start of line position userspace has accessed + * @textbuf_list: List head to store all the lines of text + * @max_line_size: Maximum size in memory allowed for a line of text + * @max_nr_lines: Maximum number of lines permitted in this textbuf + * @nr_lines: Number of entries in textbuf_list + * @flags: Flags indicating state of the textbuf, using values + * from enum kutf_helper_textbuf_flag + * @user_opened_wq: Waitq for when there's at least one userspace client + * connected to the textbuf via an open() call + * @not_full_wq: Waitq for when the textbuf can be enqueued into/can + * consume data from userspace + * @not_empty_wq: Waitq for when the textbuf can be dequeued from/can + * produce data for userspace + */ + +struct kutf_helper_textbuf { + struct mutex lock; + int nr_user_clients; + struct kutf_mempool *mempool; + char *scratchpad; + int used_bytes; + loff_t prev_pos; + loff_t prev_line_pos; + struct list_head textbuf_list; + int max_line_size; + int max_nr_lines; + int nr_lines; + unsigned long flags; + wait_queue_head_t user_opened_wq; + wait_queue_head_t not_full_wq; + wait_queue_head_t not_empty_wq; + +}; + +/* stock callbacks for userspace to read from/write to the 'data' file as a + * textbuf */ +extern struct kutf_userdata_ops kutf_helper_textbuf_userdata_ops; + +/** + * kutf_helper_textbuf_init() - init a textbuf for use as a 'data' file + * consumer/producer + * @textbuf: textbuf to initialize + * @mempool: mempool to allocate from + * @max_line_size: maximum line size expected to/from userspace + * @max_nr_lines: maximum number of lines to expect to/from userspace + * + * Initialize a textbuf so that it can consume writes made to the 'data' file, + * and produce reads for userspace on the 'data' file. Tests may then read the + * lines written by userspace, or fill the buffer so it may be read back by + * userspace. + * + * The caller should write the @textbuf pointer into the kutf_context's + * userdata_producer_priv or userdata_consumer_priv member during fixture + * creation. + * + * Usually a test will have separate textbufs for userspace to write to and + * read from. Using the same one for both will echo back to the user what they + * are writing. + * + * Lines are understood as being separated by the '\n' character, but no '\n' + * characters will be observed by the test + * + * @max_line_size puts an upper bound on the size of lines in a textbuf, + * including the \0 terminator. Lines exceeding this will be truncated, + * effectively ignoring incoming data until the next '\n' + * + * Combining this with @max_nr_lines puts an upper bound on the size of the + * file read in + * + * Return: 0 on success, or negative value on error. + */ +int kutf_helper_textbuf_init(struct kutf_helper_textbuf *textbuf, + struct kutf_mempool *mempool, int max_line_size, + int max_nr_lines); + +/** + * kutf_helper_textbuf_wait_for_user() - wait for userspace to open the 'data' + * file + * @textbuf: textbuf to wait on + * + * This can be used to synchronize with userspace so that subsequent calls to + * kutf_helper_textbuf_dequeue() and kutf_helper_textbuf_enqueue() should + * succeed. + * + * Waiting is done on a timeout. + * + * There is of course no guarantee that userspace will keep the file open after + * this, but any error in the dequeue/enqueue functions afterwards can be + * treated as such rather than "we're still waiting for userspace to begin" + * + * Return: 0 if waited successfully, -ETIMEDOUT if we exceeded the + * timeout, or some other negative value if there was an + * error during waiting. + */ + +int kutf_helper_textbuf_wait_for_user(struct kutf_helper_textbuf *textbuf); + + +/** + * kutf_helper_textbuf_dequeue() - dequeue a line from a textbuf + * @textbuf: textbuf dequeue a line as a string from + * @str_size: pointer to storage to receive the size of the string, + * which includes the '\0' terminator, or NULL if not + * required + * + * Dequeue (remove) a line from the start of the textbuf as a string, and + * return it. + * + * If no lines are available, then this will block until a line has been + * submitted. If a userspace client is not connected and there are no remaining + * lines, then this function returns NULL instead. + * + * The memory for the string comes from the kutf_mempool given during + * initialization of the textbuf, and shares the same lifetime as it. + * + * Return: pointer to the next line of the textbuf. NULL indicated + * all userspace clients disconnected. An error value to be + * checked with IS_ERR() family of functions if a signal or + * some other error occurred + */ +char *kutf_helper_textbuf_dequeue(struct kutf_helper_textbuf *textbuf, + int *str_size); + +/** + * kutf_helper_textbuf_enqueue() - enqueue a line to a textbuf + * @textbuf: textbuf to enqueue a line as a string to + * @enqueue_str: pointer to the string to enqueue to the textbuf + * @buf_max_size: maximum size of the buffer holding @enqueue_str + * + * Enqueue (add) a line to the end of a textbuf as a string. + * + * The caller should avoid placing '\n' characters in their strings, as these + * will not be split into multiple lines. + * + * A copy of the string will be made into the textbuf, so @enqueue_str can be + * freed immediately after if.the caller wishes to do so. + * + * If the maximum amount of lines has been reached, then this will block until + * a line has been removed to make space. If a userspace client is not + * connected and there is no space available, then this function returns + * -EBUSY. + * + * Return: 0 on success, or negative value on error + */ +int kutf_helper_textbuf_enqueue(struct kutf_helper_textbuf *textbuf, + char *enqueue_str, int buf_max_size); + +#endif /* _KERNEL_UTF_HELPERS_H_ */ diff --git a/mali_kbase/tests/include/kutf/kutf_helpers_user.h b/mali_kbase/tests/include/kutf/kutf_helpers_user.h new file mode 100644 index 0000000..759bf71 --- /dev/null +++ b/mali_kbase/tests/include/kutf/kutf_helpers_user.h @@ -0,0 +1,179 @@ +/* + * + * (C) COPYRIGHT 2017 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 licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +#ifndef _KERNEL_UTF_HELPERS_USER_H_ +#define _KERNEL_UTF_HELPERS_USER_H_ + +/* kutf_helpers.h + * Test helper functions for the kernel UTF test infrastructure, whose + * implementation mirrors that of similar functions for kutf-userside + */ + +#include <kutf/kutf_suite.h> +#include <kutf/kutf_helpers.h> + + +#define KUTF_HELPER_MAX_VAL_NAME_LEN 255 + +enum kutf_helper_valtype { + KUTF_HELPER_VALTYPE_INVALID, + KUTF_HELPER_VALTYPE_U64, + KUTF_HELPER_VALTYPE_STR, + + KUTF_HELPER_VALTYPE_COUNT /* Must be last */ +}; + +struct kutf_helper_named_val { + enum kutf_helper_valtype type; + char *val_name; + union { + u64 val_u64; + char *val_str; + } u; +}; + +/* Extra error values for certain helpers when we want to distinguish between + * Linux's own error values too. + * + * These can only be used on certain functions returning an int type that are + * documented as returning one of these potential values, they cannot be used + * from functions return a ptr type, since we can't decode it with PTR_ERR + * + * No negative values are used - Linux error codes should be used instead, and + * indicate a problem in accessing the data file itself (are generally + * unrecoverable) + * + * Positive values indicate correct access but invalid parsing (can be + * recovered from assuming data in the future is correct) */ +enum kutf_helper_err { + /* No error - must be zero */ + KUTF_HELPER_ERR_NONE = 0, + /* Named value parsing encountered an invalid name */ + KUTF_HELPER_ERR_INVALID_NAME, + /* Named value parsing of string or u64 type encountered extra + * characters after the value (after the last digit for a u64 type or + * after the string end delimiter for string type) */ + KUTF_HELPER_ERR_CHARS_AFTER_VAL, + /* Named value parsing of string type couldn't find the string end + * delimiter. + * + * This cannot be encountered when the NAME="value" message exceeds the + * textbuf's maximum line length, because such messages are not checked + * for an end string delimiter */ + KUTF_HELPER_ERR_NO_END_DELIMITER, + /* Named value didn't parse as any of the known types */ + KUTF_HELPER_ERR_INVALID_VALUE, +}; + + +/* textbuf Send named NAME=value pair, u64 value + * + * NAME must match [A-Z0-9_]\+ and can be up to MAX_VAL_NAME_LEN characters long + * + * This is assuming the kernel-side test is using the 'textbuf' helpers + * + * Any failure will be logged on the suite's current test fixture + * + * Returns 0 on success, non-zero on failure + */ +int kutf_helper_textbuf_send_named_u64(struct kutf_context *context, + struct kutf_helper_textbuf *textbuf, char *val_name, u64 val); + +/* Get the maximum length of a string that can be represented as a particular + * NAME="value" pair without string-value truncation in the kernel's buffer + * + * Given val_name and the kernel buffer's size, this can be used to determine + * the maximum length of a string that can be sent as val_name="value" pair + * without having the string value truncated. Any string longer than this will + * be truncated at some point during communication to this size. + * + * The calculation is valid both for sending strings of val_str_len to kernel, + * and for receiving a string that was originally val_str_len from the kernel. + * + * It is assumed that valname is a valid name for + * kutf_test_helpers_textbuf_send_named_str(), and no checking will be made to + * ensure this. + * + * Returns the maximum string length that can be represented, or a negative + * value if the NAME="value" encoding itself wouldn't fit in kern_buf_sz + */ +int kutf_helper_textbuf_max_str_len_for_kern(char *val_name, int kern_buf_sz); + +/* textbuf Send named NAME="str" pair + * + * no escaping allowed in str. Any of the following characters will terminate + * the string: '"' '\\' '\n' + * + * NAME must match [A-Z0-9_]\+ and can be up to MAX_VAL_NAME_LEN characters long + * + * This is assuming the kernel-side test is using the 'textbuf' helpers + * + * Any failure will be logged on the suite's current test fixture + * + * Returns 0 on success, non-zero on failure */ +int kutf_helper_textbuf_send_named_str(struct kutf_context *context, + struct kutf_helper_textbuf *textbuf, char *val_name, + char *val_str); + +/* textbuf Receive named NAME=value pair + * + * This can receive u64 and string values - check named_val->type + * + * If you are not planning on dynamic handling of the named value's name and + * type, then kutf_test_helpers_textbuf_receive_check_val() is more useful as a + * convenience function. + * + * String members of named_val will come from memory allocated on the fixture's mempool + * + * Returns 0 on success. Negative value on failure to receive from the 'data' + * file, positive value indicates an enum kutf_helper_err value for correct + * reception of data but invalid parsing */ +int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_val, + struct kutf_helper_textbuf *textbuf); + +/* textbuf Receive and validate NAME=value pair + * + * As with kutf_test_helpers_textbuf_receive_named_val, but validate that the + * name and type are as expected, as a convenience for a common pattern found + * in tests. + * + * NOTE: this only returns an error value if there was actually a problem + * receiving data. + * + * NOTE: If the underlying data was received correctly, but: + * - isn't of the expected name + * - isn't the expected type + * - isn't correctly parsed for the type + * then the following happens: + * - failure result is recorded + * - named_val->type will be KUTF_HELPER_VALTYPE_INVALID + * - named_val->u will contain some default value that should be relatively + * harmless for the test, including being writable in the case of string + * values + * - return value will be 0 to indicate success + * + * The rationale behind this is that we'd prefer to continue the rest of the + * test with failures propagated, rather than hitting a timeout */ +int kutf_helper_textbuf_receive_check_val(struct kutf_helper_named_val *named_val, + struct kutf_context *context, struct kutf_helper_textbuf *textbuf, + char *expect_val_name, enum kutf_helper_valtype expect_val_type); + +/* Output a named value to kmsg */ +void kutf_helper_output_named_val(struct kutf_helper_named_val *named_val); + + +#endif /* _KERNEL_UTF_HELPERS_USER_H_ */ diff --git a/mali_kbase/tests/include/kutf/kutf_mem.h b/mali_kbase/tests/include/kutf/kutf_mem.h index 0d145e4..584c9dd 100644 --- a/mali_kbase/tests/include/kutf/kutf_mem.h +++ b/mali_kbase/tests/include/kutf/kutf_mem.h @@ -30,14 +30,17 @@ */ #include <linux/list.h> +#include <linux/mutex.h> /** * struct kutf_mempool - the memory pool context management structure * @head: list head on which the allocations in this context are added to + * @lock: mutex for concurrent allocation from multiple threads * */ struct kutf_mempool { struct list_head head; + struct mutex lock; }; /** diff --git a/mali_kbase/tests/include/kutf/kutf_suite.h b/mali_kbase/tests/include/kutf/kutf_suite.h index 754c3ad..cba2b2d 100644 --- a/mali_kbase/tests/include/kutf/kutf_suite.h +++ b/mali_kbase/tests/include/kutf/kutf_suite.h @@ -26,6 +26,8 @@ * of each test. */ +#include <linux/kref.h> + #include <kutf/kutf_mem.h> #include <kutf/kutf_resultset.h> @@ -147,7 +149,29 @@ union kutf_callback_data { }; /** + * struct kutf_userdata_ops- Structure defining methods to exchange data + * with userspace via the 'data' file + * @open: Function used to notify when the 'data' file was opened + * @release: Function used to notify when the 'data' file was closed + * @notify_ended: Function used to notify when the test has ended. + * @consumer: Function used to consume writes from userspace + * @producer: Function used to produce data for userspace to read + * + * All ops can be NULL. + */ +struct kutf_userdata_ops { + int (*open)(void *priv); + void (*release)(void *priv); + void (*notify_ended)(void *priv); + ssize_t (*consumer)(void *priv, const char __user *userbuf, + size_t userbuf_len, loff_t *ppos); + ssize_t (*producer)(void *priv, char __user *userbuf, + size_t userbuf_len, loff_t *ppos); +}; + +/** * struct kutf_context - Structure representing a kernel test context + * @kref: Refcount for number of users of this context * @suite: Convenience pointer to the suite this context * is running * @test_fix: The fixture that is being run in this context @@ -161,8 +185,16 @@ union kutf_callback_data { * @status: The status of the currently running fixture. * @expected_status: The expected status on exist of the currently * running fixture. + * @userdata_consumer_priv: Parameter to pass into kutf_userdata_ops + * consumer function. Must not be NULL if a + * consumer function was specified + * @userdata_producer_priv: Parameter to pass into kutf_userdata_ops + * producer function. Must not be NULL if a + * producer function was specified + * @userdata_dentry: The debugfs file for userdata exchange */ struct kutf_context { + struct kref kref; struct kutf_suite *suite; struct kutf_test_fixture *test_fix; struct kutf_mempool fixture_pool; @@ -173,6 +205,9 @@ struct kutf_context { struct kutf_result_set *result_set; enum kutf_result_status status; enum kutf_result_status expected_status; + void *userdata_consumer_priv; + void *userdata_producer_priv; + struct dentry *userdata_dentry; }; /** @@ -345,7 +380,7 @@ void kutf_add_test_with_filters(struct kutf_suite *suite, * @name: The name of the test. * @execute: Callback to the test function to run. * @filters: A set of filtering flags, assigning test categories. - * @test_data: Test specific callback data, provoided during the + * @test_data: Test specific callback data, provided during the * running of the test in the kutf_context */ void kutf_add_test_with_filters_and_data( @@ -356,6 +391,31 @@ void kutf_add_test_with_filters_and_data( unsigned int filters, union kutf_callback_data test_data); +/** + * kutf_add_test_with_filters_data_and_userdata() - Add a test to a kernel test suite with filters and setup for + * receiving data from userside + * @suite: The suite to add the test to. + * @id: The ID of the test. + * @name: The name of the test. + * @execute: Callback to the test function to run. + * @filters: A set of filtering flags, assigning test categories. + * @test_data: Test specific callback data, provided during the + * running of the test in the kutf_context + * @userdata_ops: Callbacks to use for sending and receiving data to + * userspace. A copy of the struct kutf_userdata_ops is + * taken. Each callback can be NULL. + * + */ +void kutf_add_test_with_filters_data_and_userdata( + 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); + + /* ============================================================================ Test functions ============================================================================ */ diff --git a/mali_kbase/tests/kutf/Kbuild b/mali_kbase/tests/kutf/Kbuild index 6b840c2..97f8005 100644 --- a/mali_kbase/tests/kutf/Kbuild +++ b/mali_kbase/tests/kutf/Kbuild @@ -17,4 +17,4 @@ ccflags-y += -I$(src)/../include obj-$(CONFIG_MALI_KUTF) += kutf.o -kutf-y := kutf_mem.o kutf_resultset.o kutf_suite.o kutf_utils.o +kutf-y := kutf_mem.o kutf_resultset.o kutf_suite.o kutf_utils.o kutf_helpers.o kutf_helpers_user.o diff --git a/mali_kbase/tests/kutf/kutf_helpers.c b/mali_kbase/tests/kutf/kutf_helpers.c new file mode 100644 index 0000000..793d58c --- /dev/null +++ b/mali_kbase/tests/kutf/kutf_helpers.c @@ -0,0 +1,768 @@ +/* + * + * (C) COPYRIGHT 2017 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 licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/* Kernel UTF test helpers */ +#include <kutf/kutf_helpers.h> + +/* 10s timeout for user thread to open the 'data' file once the test is started */ +#define USERDATA_WAIT_TIMEOUT_MS 10000 +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/preempt.h> +#include <linux/wait.h> +#include <linux/uaccess.h> + + +int kutf_helper_textbuf_init(struct kutf_helper_textbuf *textbuf, + struct kutf_mempool *mempool, int max_line_size, + int max_nr_lines) +{ + textbuf->scratchpad = kutf_mempool_alloc(mempool, max_line_size); + + if (!textbuf->scratchpad) + return -ENOMEM; + + mutex_init(&textbuf->lock); + textbuf->nr_user_clients = 0; + textbuf->mempool = mempool; + textbuf->used_bytes = 0; + textbuf->prev_pos = 0; + textbuf->prev_line_pos = 0; + INIT_LIST_HEAD(&textbuf->textbuf_list); + textbuf->max_line_size = max_line_size; + textbuf->max_nr_lines = max_nr_lines; + textbuf->nr_lines = 0; + textbuf->flags = 0ul; + init_waitqueue_head(&textbuf->user_opened_wq); + init_waitqueue_head(&textbuf->not_full_wq); + init_waitqueue_head(&textbuf->not_empty_wq); + + return 0; +} +EXPORT_SYMBOL(kutf_helper_textbuf_init); + +/** + * kutf_helper_textbuf_open() - Notify that userspace has opened the 'data' + * file for a textbuf + * + * @priv: private pointer from a kutf_userdata_exchange, which + * should be a pointer to a struct kutf_helper_textbuf + * + * Return: 0 on success, or negative value on error. + */ +static int kutf_helper_textbuf_open(void *priv) +{ + struct kutf_helper_textbuf *textbuf = priv; + int ret; + + ret = mutex_lock_interruptible(&textbuf->lock); + if (ret) + return -ERESTARTSYS; + + ++(textbuf->nr_user_clients); + wake_up(&textbuf->user_opened_wq); + + mutex_unlock(&textbuf->lock); + return ret; +} + +/** + * kutf_helper_textbuf_release() - Notify that userspace has closed the 'data' + * file for a textbuf + * + * @priv: private pointer from a kutf_userdata_exchange, which + * should be a pointer to a struct kutf_helper_textbuf + */ +static void kutf_helper_textbuf_release(void *priv) +{ + struct kutf_helper_textbuf *textbuf = priv; + + /* Shouldn't use interruptible variants here because if a signal is + * pending, we can't abort and restart the call */ + mutex_lock(&textbuf->lock); + + --(textbuf->nr_user_clients); + if (!textbuf->nr_user_clients) { + /* All clients disconnected, wakeup kernel-side waiters */ + wake_up(&textbuf->not_full_wq); + wake_up(&textbuf->not_empty_wq); + } + + mutex_unlock(&textbuf->lock); +} + +/** + * kutf_helper_textbuf_notify_test_ended() - Notify that the test has ended + * + * @priv: private pointer from a kutf_userdata_exchange, which + * should be a pointer to a struct kutf_helper_textbuf + * + * After this call, userspace should be allowed to finish remaining reads but + * not make new ones, and not be allowed to make new writes. + */ +static void kutf_helper_textbuf_notify_test_ended(void *priv) +{ + struct kutf_helper_textbuf *textbuf = priv; + + /* Shouldn't use interruptible variants here because if a signal is + * pending, we can't abort and restart the call */ + mutex_lock(&textbuf->lock); + + textbuf->flags |= KUTF_HELPER_TEXTBUF_FLAG_DYING; + + /* Consumers waiting due to being full should wake up and abort */ + wake_up(&textbuf->not_full_wq); + /* Producers waiting due to being empty should wake up and abort */ + wake_up(&textbuf->not_empty_wq); + + mutex_unlock(&textbuf->lock); +} + +/* Collect text in a textbuf scratchpad up to (but excluding) specified + * newline_off, and add it as a textbuf_line + * + * newline_off is permissible to be at the character after the end of the + * scratchpad (i.e. equal to textbuf->max_line_size), for handling when the + * line was longer than the size of the scratchpad. Nevertheless, the resulting + * size of the line is kept at textbuf->max_line_size, including the '\0' + * terminator. That is, the string length will be textbuf->max_line_size-1. + * + * Remaining characters strictly after newline_off are moved to the beginning + * of the scratchpad, to allow space for a longer line to be collected. This + * means the character specified at newline_off will be removed from/no longer + * be within the valid region of the scratchpad + * + * Returns number of bytes the scratchpad was shortened by, or an error + * otherwise + */ +static size_t collect_line(struct kutf_helper_textbuf *textbuf, int newline_off) +{ + /* '\n' terminator will be replaced as '\0' */ + int str_buf_size; + struct kutf_helper_textbuf_line *textbuf_line; + char *str_start; + int bytes_remain; + char *scratch = textbuf->scratchpad; + int nextline_off; + + str_buf_size = newline_off + 1; + if (str_buf_size > textbuf->max_line_size) + str_buf_size = textbuf->max_line_size; + + /* String is stored immediately after the line */ + textbuf_line = kutf_mempool_alloc(textbuf->mempool, str_buf_size + sizeof(struct kutf_helper_textbuf_line)); + if (!textbuf_line) + return -ENOMEM; + + str_start = &textbuf_line->str[0]; + + /* Copy in string, excluding the terminating '\n' character, replacing + * it with '\0' */ + strncpy(str_start, scratch, str_buf_size - 1); + str_start[str_buf_size-1] = '\0'; + textbuf_line->str_size = str_buf_size; + + /* Append to the textbuf */ + list_add_tail(&textbuf_line->node, &textbuf->textbuf_list); + ++(textbuf->nr_lines); + + /* Move the rest of the scratchpad to the start */ + nextline_off = newline_off + 1; + if (nextline_off > textbuf->used_bytes) + nextline_off = textbuf->used_bytes; + + bytes_remain = textbuf->used_bytes - nextline_off; + memmove(scratch, scratch + nextline_off, bytes_remain); + textbuf->used_bytes = bytes_remain; + + /* Wakeup anyone blocked on empty */ + wake_up(&textbuf->not_empty_wq); + + return nextline_off; +} + +/* Buffer size for truncating a string to its newline. + * Allocated on the stack, so keep it moderately small (within PAGE_SIZE) */ +#define TRUNCATE_BUF_SZ 512 + +/* Discard input from a userbuf up to a newline, then collect what was in the + * scratchpad into a new textbuf line */ +static ssize_t collect_longline_truncate(struct kutf_helper_textbuf *textbuf, + const char __user *userbuf, size_t userbuf_len) +{ + ssize_t bytes_processed = 0; + + while (userbuf_len > 0) { + int userbuf_copy_sz = userbuf_len; + size_t res; + char *newline_ptr; + char truncate_buf[TRUNCATE_BUF_SZ]; + + if (userbuf_len > TRUNCATE_BUF_SZ) + userbuf_copy_sz = TRUNCATE_BUF_SZ; + else + userbuf_copy_sz = (int)userbuf_len; + + /* copy what we can */ + res = copy_from_user(truncate_buf, userbuf, userbuf_copy_sz); + if (res == userbuf_copy_sz) + return -EFAULT; + userbuf_copy_sz -= res; + + /* Search for newline in what was copied */ + newline_ptr = strnchr(truncate_buf, userbuf_copy_sz, '\n'); + + if (newline_ptr) { + ssize_t sres; + /* Newline found: collect scratchpad and exit out */ + int newline_off = newline_ptr - truncate_buf; + + sres = collect_line(textbuf, textbuf->used_bytes); + if (sres < 0) + return sres; + + bytes_processed += newline_off + 1; + break; + } + + /* Newline not yet found: advance to the next part to copy */ + userbuf += userbuf_copy_sz; + userbuf_len -= userbuf_copy_sz; + bytes_processed += userbuf_copy_sz; + } + + return bytes_processed; +} + +/** + * kutf_helper_textbuf_consume() - 'data' file consumer function for writing to + * a textbuf + * @priv: private pointer from a kutf_userdata_exchange, which + * should be a pointer to a struct kutf_helper_textbuf to + * write into + * @userbuf: the userspace buffer to read from + * @userbuf_len: size of the userspace buffer + * @ppos: the current position in the buffer + * + * This consumer function is used as a write consumer for the 'data' file, + * receiving data that has been written to the 'data' file by userspace. It + * will read from the userspace buffer @userbuf and separates it into '\n' + * delimited lines for the textbuf pointed to by @priv . + * + * If there is insufficient space in textbuf, then it will block until there is + * space - for example, a kernel-side test calls + * kutf_helper_textbuf_dequeue(). Since this is expected to be called in the + * context of a syscall, the call can only be cancelled by sending an + * appropriate signal to the userspace process. + * + * The current position @ppos is advanced by the number of bytes successfully + * read. + * + * Return: the number of bytes read, or negative value on error. + */ +static ssize_t kutf_helper_textbuf_consume(void *priv, + const char __user *userbuf, size_t userbuf_len, loff_t *ppos) +{ + struct kutf_helper_textbuf *textbuf = priv; + int userbuf_copy_sz; + char *next_newline_ptr; + size_t bytes_processed = 0; + int newdata_off; + ssize_t ret; + + ret = mutex_lock_interruptible(&textbuf->lock); + if (ret) + return -ERESTARTSYS; + + /* Validate input */ + if (*ppos < 0) { + ret = -EINVAL; + goto out_unlock; + } + if (!userbuf_len) { + ret = 0; + goto out_unlock; + } + + while (textbuf->nr_lines >= textbuf->max_nr_lines && + !(textbuf->flags & KUTF_HELPER_TEXTBUF_FLAG_DYING)) { + /* Block on kernel-side dequeue making space available + * NOTE: should also handle O_NONBLOCK */ + mutex_unlock(&textbuf->lock); + ret = wait_event_interruptible(textbuf->not_full_wq, + (textbuf->nr_lines < textbuf->max_nr_lines || + (textbuf->flags & KUTF_HELPER_TEXTBUF_FLAG_DYING))); + if (ret) + return -ERESTARTSYS; + ret = mutex_lock_interruptible(&textbuf->lock); + if (ret) + return -ERESTARTSYS; + } + + if (textbuf->flags & KUTF_HELPER_TEXTBUF_FLAG_DYING) { + ret = -ENODEV; + goto out_unlock; + } + + if (textbuf->prev_pos != *ppos && textbuf->used_bytes) { + /* Seeking causes a new line to occur: + * Truncate what data was there into a textbuf-line, and reset + * the buffer */ + ret = collect_line(textbuf, textbuf->used_bytes); + if (ret < 0) + goto finish; + } else if (textbuf->used_bytes >= (textbuf->max_line_size - 1)) { + /* Line too long discard input until we find a '\n' */ + ret = collect_longline_truncate(textbuf, userbuf, userbuf_len); + + if (ret < 0) + goto finish; + + /* Update userbuf with how much was processed, which may be the + * entire buffer now */ + userbuf += ret; + userbuf_len -= ret; + bytes_processed += ret; + + /* If there's buffer remaining and we fault later (e.g. can't + * read or OOM) ensure ppos is updated */ + *ppos += ret; + + /* recheck in case entire buffer processed */ + if (!userbuf_len) + goto finish; + } + + /* An extra line may've been added, ensure we don't overfill */ + if (textbuf->nr_lines >= textbuf->max_nr_lines) + goto finish_noerr; + + userbuf_copy_sz = userbuf_len; + + /* Copy in as much as we can */ + if (userbuf_copy_sz > textbuf->max_line_size - textbuf->used_bytes) + userbuf_copy_sz = textbuf->max_line_size - textbuf->used_bytes; + + ret = copy_from_user(textbuf->scratchpad + textbuf->used_bytes, userbuf, userbuf_copy_sz); + if (ret == userbuf_copy_sz) { + ret = -EFAULT; + goto finish; + } + userbuf_copy_sz -= ret; + + newdata_off = textbuf->used_bytes; + textbuf->used_bytes += userbuf_copy_sz; + + while (textbuf->used_bytes && textbuf->nr_lines < textbuf->max_nr_lines) { + int new_bytes_remain = textbuf->used_bytes - newdata_off; + /* Find a new line - only the new part should be checked */ + next_newline_ptr = strnchr(textbuf->scratchpad + newdata_off, new_bytes_remain, '\n'); + + if (next_newline_ptr) { + int newline_off = next_newline_ptr - textbuf->scratchpad; + + /* if found, collect up to it, then memmove the rest */ + /* reset positions and see if we can fill any further */ + /* repeat until run out of data or line is filled */ + ret = collect_line(textbuf, newline_off); + + /* If filled up or OOM, rollback the remaining new + * data. Instead we'll try to grab it next time we're + * called */ + if (textbuf->nr_lines >= textbuf->max_nr_lines || ret < 0) + textbuf->used_bytes = newdata_off; + + if (ret < 0) + goto finish; + + /* Fix up ppos etc in case we'll be ending the loop */ + *ppos += ret - newdata_off; + bytes_processed += ret - newdata_off; + newdata_off = 0; + } else { + /* there's bytes left, but no new-line, so try to fill up next time */ + *ppos += new_bytes_remain; + bytes_processed += new_bytes_remain; + break; + } + } + +finish_noerr: + ret = bytes_processed; +finish: + textbuf->prev_pos = *ppos; +out_unlock: + mutex_unlock(&textbuf->lock); + + return ret; +} + +/** + * kutf_helper_textbuf_produce() - 'data' file producer function for reading + * from a textbuf + * @priv: private pointer from a kutf_userdata_exchange, which + * should be a pointer to a struct kutf_helper_textbuf to + * read from + * @userbuf: the userspace buffer to write to + * @userbuf_len: size of the userspace buffer + * @ppos: the current position in the buffer + * + * This producer function is used as a read producer for the 'data' file, + * allowing userspace to read from the 'data' file. It will write to the + * userspace buffer @userbuf, taking lines from the textbuf pointed to by + * @priv, separating each line with '\n'. + * + * If there is no data in the textbuf, then it will block until some appears - + * for example, a kernel-side test calls kutf_helper_textbuf_enqueue(). Since + * this is expected to be called in the context of a syscall, the call can only + * be cancelled by sending an appropriate signal to the userspace process. + * + * The current position @ppos is advanced by the number of bytes successfully + * written. + * + * Return: the number of bytes written, or negative value on error + */ +static ssize_t kutf_helper_textbuf_produce(void *priv, char __user *userbuf, + size_t userbuf_len, loff_t *ppos) +{ + struct kutf_helper_textbuf *textbuf = priv; + loff_t pos_offset; + struct kutf_helper_textbuf_line *line = NULL; + int line_start_pos; + size_t bytes_processed = 0; + ssize_t ret; + int copy_length; + + ret = mutex_lock_interruptible(&textbuf->lock); + if (ret) + return -ERESTARTSYS; + + /* Validate input */ + if (*ppos < 0) { + ret = -EINVAL; + goto finish; + } + if (!userbuf_len) { + ret = 0; + goto finish; + } + + /* Seeking to before the beginning of the line will have the effect of + * resetting the position to the start of the current data, since we've + * already discarded previous data */ + if (*ppos < textbuf->prev_line_pos) + textbuf->prev_line_pos = *ppos; + + while (!line) { + int needs_wake = 0; + + pos_offset = *ppos - textbuf->prev_line_pos; + line_start_pos = 0; + + /* Find the line for the offset, emptying the textbuf as we go */ + while (!list_empty(&textbuf->textbuf_list)) { + int line_end_pos; + + line = list_first_entry(&textbuf->textbuf_list, struct kutf_helper_textbuf_line, node); + + /* str_size used in line_end_pos because lines implicitly have + * a '\n', but we count the '\0' string terminator as that */ + line_end_pos = line_start_pos + line->str_size; + + if (pos_offset < line_end_pos) + break; + + line_start_pos += line->str_size; + /* Only discard a line when we're sure it's finished + * with, to avoid awkward rollback conditions if we've + * had to block */ + list_del(&line->node); + --(textbuf->nr_lines); + line = NULL; + needs_wake = 1; + } + + /* Update the start of the line pos for next time we're called */ + textbuf->prev_line_pos += line_start_pos; + + /* If space was freed up, wake waiters */ + if (needs_wake) + wake_up(&textbuf->not_full_wq); +; + if (!line) { + /* Only check before waiting, to ensure if the test + * does the last enqueue and immediately finishes, then + * we'll go back round the loop to receive the line + * instead of just dying straight away */ + if (textbuf->flags & KUTF_HELPER_TEXTBUF_FLAG_DYING) { + /* Indicate EOF rather than an error */ + ret = 0; + goto finish; + } + + /* No lines found, block for new ones + * NOTE: should also handle O_NONBLOCK */ + mutex_unlock(&textbuf->lock); + ret = wait_event_interruptible(textbuf->not_empty_wq, + (textbuf->nr_lines > 0 || + (textbuf->flags & KUTF_HELPER_TEXTBUF_FLAG_DYING))); + + /* signals here are not restartable */ + if (ret) + return ret; + ret = mutex_lock_interruptible(&textbuf->lock); + if (ret) + return ret; + } + + } + + + /* Find offset within the line, guaranteed to be within line->str_size */ + pos_offset -= line_start_pos; + + while (userbuf_len && line) { + /* Copy at most to the end of string, excluding terminator */ + copy_length = line->str_size - 1 - pos_offset; + if (copy_length > userbuf_len) + copy_length = userbuf_len; + + if (copy_length) { + ret = copy_to_user(userbuf, &line->str[pos_offset], copy_length); + if (ret == copy_length) { + ret = -EFAULT; + goto finish; + } + copy_length -= ret; + + userbuf += copy_length; + userbuf_len -= copy_length; + bytes_processed += copy_length; + *ppos += copy_length; + if (ret) + goto finish_noerr; + } + + /* Add terminator if one was needed */ + if (userbuf_len) { + copy_length = 1; + ret = copy_to_user(userbuf, "\n", copy_length); + if (ret == copy_length) { + ret = -EFAULT; + goto finish; + } + copy_length -= ret; + + userbuf += copy_length; + userbuf_len -= copy_length; + bytes_processed += copy_length; + *ppos += copy_length; + } else { + /* string wasn't completely copied this time - try to + * finish it next call */ + break; + } + + /* Line Completed - only now can safely delete it */ + textbuf->prev_line_pos += line->str_size; + list_del(&line->node); + --(textbuf->nr_lines); + line = NULL; + /* Space freed up, wake up waiters */ + wake_up(&textbuf->not_full_wq); + + /* Pick the next line */ + if (!list_empty(&textbuf->textbuf_list)) { + line = list_first_entry(&textbuf->textbuf_list, struct kutf_helper_textbuf_line, node); + pos_offset = 0; + } + /* if no more lines, we've copied at least some bytes, so only + * need to block on new lines the next time we're called */ + } + +finish_noerr: + ret = bytes_processed; +finish: + mutex_unlock(&textbuf->lock); + + return ret; +} + +int kutf_helper_textbuf_wait_for_user(struct kutf_helper_textbuf *textbuf) +{ + int err; + unsigned long now; + unsigned long timeout_jiffies = msecs_to_jiffies(USERDATA_WAIT_TIMEOUT_MS); + unsigned long time_end; + int ret = 0; + + /* Mutex locking using non-interruptible variants, since a signal to + * the user process will generally have to wait until we finish the + * test, because we can't restart the test. The exception is where + * we're blocked on a waitq */ + mutex_lock(&textbuf->lock); + + now = jiffies; + time_end = now + timeout_jiffies; + + while (!textbuf->nr_user_clients && time_before_eq(now, time_end)) { + unsigned long time_to_wait = time_end - now; + /* No users yet, block or timeout */ + mutex_unlock(&textbuf->lock); + /* Use interruptible here - in case we block for a long time + * and want to kill the user process */ + err = wait_event_interruptible_timeout(textbuf->user_opened_wq, + (textbuf->nr_user_clients > 0), time_to_wait); + /* Any error is not restartable due to how kutf runs tests */ + if (err < 0) + return -EINTR; + mutex_lock(&textbuf->lock); + + now = jiffies; + } + if (!textbuf->nr_user_clients) + ret = -ETIMEDOUT; + + mutex_unlock(&textbuf->lock); + + return ret; +} +EXPORT_SYMBOL(kutf_helper_textbuf_wait_for_user); + +char *kutf_helper_textbuf_dequeue(struct kutf_helper_textbuf *textbuf, + int *str_size) +{ + struct kutf_helper_textbuf_line *line; + char *ret = NULL; + + /* Mutex locking using non-interruptible variants, since a signal to + * the user process will generally have to wait until we finish the + * test, because we can't restart the test. The exception is where + * we're blocked on a waitq */ + mutex_lock(&textbuf->lock); + + while (list_empty(&textbuf->textbuf_list)) { + int err; + + if (!textbuf->nr_user_clients) { + /* No user-side clients - error */ + goto out; + } + + /* No lines found, block for new ones from user-side consumer */ + mutex_unlock(&textbuf->lock); + /* Use interruptible here - in case we block for a long time + * and want to kill the user process */ + err = wait_event_interruptible(textbuf->not_empty_wq, + (textbuf->nr_lines > 0 || !textbuf->nr_user_clients)); + /* Any error is not restartable due to how kutf runs tests */ + if (err) + return ERR_PTR(-EINTR); + mutex_lock(&textbuf->lock); + } + + line = list_first_entry(&textbuf->textbuf_list, struct kutf_helper_textbuf_line, node); + list_del(&line->node); + --(textbuf->nr_lines); + /* Space freed up, wake up waiters */ + wake_up(&textbuf->not_full_wq); + + if (str_size) + *str_size = line->str_size; + + ret = &line->str[0]; + +out: + mutex_unlock(&textbuf->lock); + return ret; +} +EXPORT_SYMBOL(kutf_helper_textbuf_dequeue); + +int kutf_helper_textbuf_enqueue(struct kutf_helper_textbuf *textbuf, + char *enqueue_str, int buf_max_size) +{ + struct kutf_helper_textbuf_line *textbuf_line; + int str_size = strnlen(enqueue_str, buf_max_size) + 1; + char *str_start; + int ret = 0; + + /* Mutex locking using non-interruptible variants, since a signal to + * the user process will generally have to wait until we finish the + * test, because we can't restart the test. The exception is where + * we're blocked on a waitq */ + mutex_lock(&textbuf->lock); + + if (str_size > textbuf->max_line_size) + str_size = textbuf->max_line_size; + + while (textbuf->nr_lines >= textbuf->max_nr_lines) { + if (!textbuf->nr_user_clients) { + /* No user-side clients - error */ + ret = -EBUSY; + goto out; + } + + /* Block on user-side producer making space available */ + mutex_unlock(&textbuf->lock); + /* Use interruptible here - in case we block for a long time + * and want to kill the user process */ + ret = wait_event_interruptible(textbuf->not_full_wq, + (textbuf->nr_lines < textbuf->max_nr_lines || !textbuf->nr_user_clients)); + /* Any error is not restartable due to how kutf runs tests */ + if (ret) + return -EINTR; + mutex_lock(&textbuf->lock); + } + + /* String is stored immediately after the line */ + textbuf_line = kutf_mempool_alloc(textbuf->mempool, str_size + sizeof(struct kutf_helper_textbuf_line)); + if (!textbuf_line) { + ret = -ENOMEM; + goto out; + } + + str_start = &textbuf_line->str[0]; + + /* Copy in string */ + strncpy(str_start, enqueue_str, str_size); + /* Enforce the '\0' termination */ + str_start[str_size-1] = '\0'; + textbuf_line->str_size = str_size; + + /* Append to the textbuf */ + list_add_tail(&textbuf_line->node, &textbuf->textbuf_list); + ++(textbuf->nr_lines); + + /* Wakeup anyone blocked on empty */ + wake_up(&textbuf->not_empty_wq); + +out: + mutex_unlock(&textbuf->lock); + return ret; +} +EXPORT_SYMBOL(kutf_helper_textbuf_enqueue); + + +struct kutf_userdata_ops kutf_helper_textbuf_userdata_ops = { + .open = kutf_helper_textbuf_open, + .release = kutf_helper_textbuf_release, + .notify_ended = kutf_helper_textbuf_notify_test_ended, + .consumer = kutf_helper_textbuf_consume, + .producer = kutf_helper_textbuf_produce, +}; +EXPORT_SYMBOL(kutf_helper_textbuf_userdata_ops); diff --git a/mali_kbase/tests/kutf/kutf_helpers_user.c b/mali_kbase/tests/kutf/kutf_helpers_user.c new file mode 100644 index 0000000..5c34120 --- /dev/null +++ b/mali_kbase/tests/kutf/kutf_helpers_user.c @@ -0,0 +1,462 @@ +/* + * + * (C) COPYRIGHT 2017 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 licence. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + +/* Kernel UTF test helpers that mirror those for kutf-userside */ +#include <kutf/kutf_helpers_user.h> +#include <kutf/kutf_utils.h> + +#include <linux/err.h> +#include <linux/slab.h> + +const char *valtype_names[] = { + "INVALID", + "U64", + "STR", +}; + +static const char *get_val_type_name(enum kutf_helper_valtype valtype) +{ + /* enums can be signed or unsigned (implementation dependant), so + * enforce it to prevent: + * a) "<0 comparison on unsigned type" warning - if we did both upper + * and lower bound check + * b) incorrect range checking if it was a signed type - if we did + * upper bound check only */ + unsigned int type_idx = (unsigned int)valtype; + + if (type_idx >= (unsigned int)KUTF_HELPER_VALTYPE_COUNT) + type_idx = (unsigned int)KUTF_HELPER_VALTYPE_INVALID; + + return valtype_names[type_idx]; +} + +/* Check up to str_len chars of val_str to see if it's a valid value name: + * + * - Has between 1 and KUTF_HELPER_MAX_VAL_NAME_LEN characters before the \0 terminator + * - And, each char is in the character set [A-Z0-9_] */ +static int validate_val_name(char *val_str, int str_len) +{ + int i = 0; + + for (i = 0; str_len && i <= KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0'; ++i, --str_len) { + char val_chr = val_str[i]; + + if (val_chr >= 'A' && val_chr <= 'Z') + continue; + if (val_chr >= '0' && val_chr <= '9') + continue; + if (val_chr == '_') + continue; + + /* Character not in the set [A-Z0-9_] - report error */ + return 1; + } + + /* Names of 0 length are not valid */ + if (i == 0) + return 1; + /* Length greater than KUTF_HELPER_MAX_VAL_NAME_LEN not allowed */ + if (i > KUTF_HELPER_MAX_VAL_NAME_LEN || (i == KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0')) + return 1; + + return 0; +} + +/* Find the length of the valid part of the string when it will be in quotes + * e.g. "str" + * + * That is, before any '\\', '\n' or '"' characters. This is so we don't have + * to escape the string */ +static int find_quoted_string_valid_len(char *str) +{ + char *ptr; + const char *check_chars = "\\\n\""; + + ptr = strpbrk(str, check_chars); + if (ptr) + return ptr-str; + + return strlen(str); +} + +#define MAX_U64_HEX_LEN 16 +/* (Name size) + ("=0x" size) + (64-bit hex value size) + (terminator) */ +#define NAMED_U64_VAL_BUF_SZ (KUTF_HELPER_MAX_VAL_NAME_LEN + 3 + MAX_U64_HEX_LEN + 1) + +int kutf_helper_textbuf_send_named_u64(struct kutf_context *context, + struct kutf_helper_textbuf *textbuf, char *val_name, u64 val) +{ + int ret = 1; + char msgbuf[NAMED_U64_VAL_BUF_SZ]; + const char *errmsg = NULL; + + if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send u64 value named '%s': Invalid value name", val_name); + goto out_err; + } + + ret = snprintf(msgbuf, NAMED_U64_VAL_BUF_SZ, "%s=0x%llx", val_name, val); + if (ret >= NAMED_U64_VAL_BUF_SZ || ret < 0) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send u64 value named '%s': snprintf() problem buffer size==%d ret=%d", + val_name, NAMED_U64_VAL_BUF_SZ, ret); + goto out_err; + } + msgbuf[NAMED_U64_VAL_BUF_SZ-1] = '\0'; + + ret = kutf_helper_textbuf_enqueue(textbuf, msgbuf, NAMED_U64_VAL_BUF_SZ); + if (ret) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send u64 value named '%s': send returned %d", + val_name, ret); + goto out_err; + } + + return ret; +out_err: + kutf_test_fail(context, errmsg); + return ret; +} +EXPORT_SYMBOL(kutf_helper_textbuf_send_named_u64); + +#define NAMED_VALUE_SEP "=" +#define NAMED_STR_START_DELIM NAMED_VALUE_SEP "\"" +#define NAMED_STR_END_DELIM "\"" + +int kutf_helper_textbuf_max_str_len_for_kern(char *val_name, + int kern_buf_sz) +{ + int val_name_len = strlen(val_name); + int start_delim_len = strlen(NAMED_STR_START_DELIM); + int max_msg_len = kern_buf_sz - 1; + int max_str_len; + + /* We do not include the end delimiter. Providing there is a line + * ending character when sending the message, the end delimiter can be + * truncated off safely to allow proper NAME="value" reception when + * value's length is too long */ + max_str_len = max_msg_len - val_name_len - start_delim_len; + + return max_str_len; +} +EXPORT_SYMBOL(kutf_helper_textbuf_max_str_len_for_kern); + +int kutf_helper_textbuf_send_named_str(struct kutf_context *context, + struct kutf_helper_textbuf *textbuf, char *val_name, + char *val_str) +{ + int val_str_len; + int str_buf_sz; + char *str_buf = NULL; + int ret = 1; + char *copy_ptr; + int val_name_len; + int start_delim_len = strlen(NAMED_STR_START_DELIM); + int end_delim_len = strlen(NAMED_STR_END_DELIM); + const char *errmsg = NULL; + + if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send u64 value named '%s': Invalid value name", val_name); + goto out_err; + } + val_name_len = strlen(val_name); + + val_str_len = find_quoted_string_valid_len(val_str); + + /* (name length) + ("=\"" length) + (val_str len) + ("\"" length) + terminator */ + str_buf_sz = val_name_len + start_delim_len + val_str_len + end_delim_len + 1; + + /* Using kmalloc() here instead of mempool since we know we need to free + * before we return */ + str_buf = kmalloc(str_buf_sz, GFP_KERNEL); + if (!str_buf) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send str value named '%s': kmalloc failed, str_buf_sz=%d", + val_name, str_buf_sz); + goto out_err; + } + copy_ptr = str_buf; + + /* Manually copy each string component instead of snprintf because + * val_str may need to end early, and less error path handling */ + + /* name */ + memcpy(copy_ptr, val_name, val_name_len); + copy_ptr += val_name_len; + + /* str start delimiter */ + memcpy(copy_ptr, NAMED_STR_START_DELIM, start_delim_len); + copy_ptr += start_delim_len; + + /* str value */ + memcpy(copy_ptr, val_str, val_str_len); + copy_ptr += val_str_len; + + /* str end delimiter */ + memcpy(copy_ptr, NAMED_STR_END_DELIM, end_delim_len); + copy_ptr += end_delim_len; + + /* Terminator */ + *copy_ptr = '\0'; + + ret = kutf_helper_textbuf_enqueue(textbuf, str_buf, str_buf_sz); + + if (ret) { + errmsg = kutf_dsprintf(&context->fixture_pool, + "Failed to send str value named '%s': send returned %d", + val_name, ret); + goto out_err; + } + + kfree(str_buf); + return ret; + +out_err: + kutf_test_fail(context, errmsg); + kfree(str_buf); + return ret; +} +EXPORT_SYMBOL(kutf_helper_textbuf_send_named_str); + +int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_val, + struct kutf_helper_textbuf *textbuf) +{ + int recv_sz; + char *recv_str; + char *search_ptr; + char *name_str = NULL; + int name_len; + int strval_len; + enum kutf_helper_valtype type = KUTF_HELPER_VALTYPE_INVALID; + char *strval = NULL; + u64 u64val = 0; + int orig_recv_sz; + int err = KUTF_HELPER_ERR_INVALID_VALUE; + + recv_str = kutf_helper_textbuf_dequeue(textbuf, &recv_sz); + if (!recv_str) + return -EBUSY; + else if (IS_ERR(recv_str)) + return PTR_ERR(recv_str); + orig_recv_sz = recv_sz; + + /* Find the '=', grab the name and validate it */ + search_ptr = strnchr(recv_str, recv_sz, NAMED_VALUE_SEP[0]); + if (search_ptr) { + name_len = search_ptr - recv_str; + if (!validate_val_name(recv_str, name_len)) { + /* no need to reallocate - just modify string in place */ + name_str = recv_str; + name_str[name_len] = '\0'; + + /* Move until after the '=' */ + recv_str += (name_len + 1); + recv_sz -= (name_len + 1); + } + } + if (!name_str) { + pr_err("Invalid name part for recevied string '%s'\n", recv_str); + return KUTF_HELPER_ERR_INVALID_NAME; + } + + /* detect value type */ + if (*recv_str == NAMED_STR_START_DELIM[1]) { + /* string delimiter start*/ + ++recv_str; + --recv_sz; + + /* Find end of string */ + search_ptr = strnchr(recv_str, recv_sz, NAMED_STR_END_DELIM[0]); + if (search_ptr) { + strval_len = search_ptr - recv_str; + /* Validate the string to ensure it contains no quotes */ + if (strval_len == find_quoted_string_valid_len(recv_str)) { + /* no need to reallocate - just modify string in place */ + strval = recv_str; + strval[strval_len] = '\0'; + + /* Move until after the end delimiter */ + recv_str += (strval_len + 1); + recv_sz -= (strval_len + 1); + type = KUTF_HELPER_VALTYPE_STR; + } else { + pr_err("String value contains invalid characters in rest of received string '%s'\n", recv_str); + err = KUTF_HELPER_ERR_CHARS_AFTER_VAL; + } + } else if (orig_recv_sz == textbuf->max_line_size) { + /* No end-delimiter found, but the line is at + * the max line size. Assume that before + * truncation the line had a closing delimiter + * anyway */ + strval_len = strlen(recv_str); + /* Validate the string to ensure it contains no quotes */ + if (strval_len == find_quoted_string_valid_len(recv_str)) { + strval = recv_str; + + /* Move to the end of the string */ + recv_str += strval_len; + recv_sz -= strval_len; + type = KUTF_HELPER_VALTYPE_STR; + } else { + pr_err("String value contains invalid characters in rest of received string '%s'\n", recv_str); + err = KUTF_HELPER_ERR_CHARS_AFTER_VAL; + } + } else { + pr_err("End of string delimiter not found in rest of received string '%s'\n", recv_str); + err = KUTF_HELPER_ERR_NO_END_DELIMITER; + } + } else { + int err; + /* possibly a number value - strtoull will parse it */ + err = kstrtoull(recv_str, 0, &u64val); + /* unlike userspace can't get an end ptr, but if kstrtoull() + * reads characters after the number it'll report -EINVAL */ + if (!err) { + int len_remain = strnlen(recv_str, recv_sz); + + type = KUTF_HELPER_VALTYPE_U64; + recv_str += len_remain; + recv_sz -= len_remain; + } else { + /* special case: not a number, report as such */ + pr_err("Rest of received string was not a numeric value or quoted string value: '%s'\n", recv_str); + err = KUTF_HELPER_ERR_INVALID_VALUE; + } + } + + if (type == KUTF_HELPER_VALTYPE_INVALID) + return err; + + /* Any remaining characters - error */ + if (strnlen(recv_str, recv_sz) != 0) { + pr_err("Characters remain after value of type %s: '%s'\n", + get_val_type_name(type), recv_str); + return KUTF_HELPER_ERR_CHARS_AFTER_VAL; + } + + /* Success - write into the output structure */ + switch (type) { + case KUTF_HELPER_VALTYPE_U64: + named_val->u.val_u64 = u64val; + break; + case KUTF_HELPER_VALTYPE_STR: + named_val->u.val_str = strval; + break; + default: + pr_err("Unreachable, fix textbuf_receive_named_val\n"); + /* Coding error, report as though 'data' file failed */ + return -EINVAL; + } + + named_val->val_name = name_str; + named_val->type = type; + + return KUTF_HELPER_ERR_NONE; +} +EXPORT_SYMBOL(kutf_helper_textbuf_receive_named_val); + +#define DUMMY_MSG "<placeholder due to test fail>" +int kutf_helper_textbuf_receive_check_val(struct kutf_helper_named_val *named_val, + struct kutf_context *context, struct kutf_helper_textbuf *textbuf, + char *expect_val_name, enum kutf_helper_valtype expect_val_type) +{ + int err; + + err = kutf_helper_textbuf_receive_named_val(named_val, textbuf); + if (err < 0) { + const char *msg = kutf_dsprintf(&context->fixture_pool, + "Failed to receive value named '%s'", + expect_val_name); + kutf_test_fail(context, msg); + return err; + } else if (err > 0) { + const char *msg = kutf_dsprintf(&context->fixture_pool, + "Named-value parse error when expecting value named '%s'", + expect_val_name); + kutf_test_fail(context, msg); + goto out_fail_and_fixup; + } + + if (strcmp(named_val->val_name, expect_val_name) != 0) { + const char *msg = kutf_dsprintf(&context->fixture_pool, + "Expecting to receive value named '%s' but got '%s'", + expect_val_name, named_val->val_name); + kutf_test_fail(context, msg); + goto out_fail_and_fixup; + } + + + if (named_val->type != expect_val_type) { + const char *msg = kutf_dsprintf(&context->fixture_pool, + "Expecting value named '%s' to be of type %s but got %s", + expect_val_name, get_val_type_name(expect_val_type), + get_val_type_name(named_val->type)); + kutf_test_fail(context, msg); + goto out_fail_and_fixup; + } + + return err; + +out_fail_and_fixup: + /* Produce a valid but incorrect value */ + switch (expect_val_type) { + case KUTF_HELPER_VALTYPE_U64: + named_val->u.val_u64 = 0ull; + break; + case KUTF_HELPER_VALTYPE_STR: + { + char *str = kutf_mempool_alloc(&context->fixture_pool, sizeof(DUMMY_MSG)); + + if (!str) + return -1; + + strcpy(str, DUMMY_MSG); + named_val->u.val_str = str; + break; + } + default: + break; + } + + /* Indicate that this is invalid */ + named_val->type = KUTF_HELPER_VALTYPE_INVALID; + + /* But at least allow the caller to continue in the test with failures */ + return 0; +} +EXPORT_SYMBOL(kutf_helper_textbuf_receive_check_val); + +void kutf_helper_output_named_val(struct kutf_helper_named_val *named_val) +{ + switch (named_val->type) { + case KUTF_HELPER_VALTYPE_U64: + pr_warn("%s=0x%llx\n", named_val->val_name, named_val->u.val_u64); + break; + case KUTF_HELPER_VALTYPE_STR: + pr_warn("%s=\"%s\"\n", named_val->val_name, named_val->u.val_str); + break; + case KUTF_HELPER_VALTYPE_INVALID: + pr_warn("%s is invalid\n", named_val->val_name); + break; + default: + pr_warn("%s has unknown type %d\n", named_val->val_name, named_val->type); + break; + } +} +EXPORT_SYMBOL(kutf_helper_output_named_val); diff --git a/mali_kbase/tests/kutf/kutf_mem.c b/mali_kbase/tests/kutf/kutf_mem.c index 5408e57..a75e15f 100644 --- a/mali_kbase/tests/kutf/kutf_mem.c +++ b/mali_kbase/tests/kutf/kutf_mem.c @@ -19,7 +19,6 @@ #include <linux/list.h> #include <linux/slab.h> -#include <linux/module.h> #include <kutf/kutf_mem.h> @@ -42,6 +41,7 @@ int kutf_mempool_init(struct kutf_mempool *pool) } INIT_LIST_HEAD(&pool->head); + mutex_init(&pool->lock); return 0; } @@ -57,6 +57,7 @@ void kutf_mempool_destroy(struct kutf_mempool *pool) return; } + mutex_lock(&pool->lock); list_for_each_safe(remove, tmp, &pool->head) { struct kutf_alloc_entry *remove_alloc; @@ -64,6 +65,8 @@ void kutf_mempool_destroy(struct kutf_mempool *pool) list_del(&remove_alloc->node); kfree(remove_alloc); } + mutex_unlock(&pool->lock); + } EXPORT_SYMBOL(kutf_mempool_destroy); @@ -76,6 +79,8 @@ void *kutf_mempool_alloc(struct kutf_mempool *pool, size_t size) goto fail_pool; } + mutex_lock(&pool->lock); + ret = kmalloc(sizeof(*ret) + size, GFP_KERNEL); if (!ret) { pr_err("Failed to allocate memory\n"); @@ -85,9 +90,12 @@ void *kutf_mempool_alloc(struct kutf_mempool *pool, size_t size) INIT_LIST_HEAD(&ret->node); list_add(&ret->node, &pool->head); + mutex_unlock(&pool->lock); + return &ret->data[0]; fail_alloc: + mutex_unlock(&pool->lock); fail_pool: return NULL; } diff --git a/mali_kbase/tests/kutf/kutf_suite.c b/mali_kbase/tests/kutf/kutf_suite.c index a7cfd3b..ad30cc8 100644 --- a/mali_kbase/tests/kutf/kutf_suite.c +++ b/mali_kbase/tests/kutf/kutf_suite.c @@ -26,6 +26,7 @@ #include <linux/uaccess.h> #include <linux/fs.h> #include <linux/version.h> +#include <linux/atomic.h> #include <generated/autoconf.h> @@ -60,6 +61,8 @@ 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; @@ -70,6 +73,7 @@ struct kutf_test_function { struct list_head node; struct list_head variant_list; struct dentry *dir; + struct kutf_userdata_ops userdata_ops; }; /** @@ -79,12 +83,14 @@ 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; @@ -125,16 +131,40 @@ ADD_UTF_RESULT(KUTF_RESULT_ABORT) * 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 - * @context: Test context to destroy + * 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_destroy_context(struct kutf_context *context); +static void kutf_context_put(struct kutf_context *context); /** * kutf_set_result() - Set the test result against the specified test context @@ -222,6 +252,263 @@ 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 */ @@ -293,10 +580,24 @@ static int kutf_debugfs_run_open(struct inode *inode, struct file *file) 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) - return -ENODEV; + if (!test_context) { + err = -ENODEV; + goto finish; + } file->private_data = test_context; @@ -310,15 +611,25 @@ static int kutf_debugfs_run_open(struct inode *inode, struct file *file) /* 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); + 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); kutf_add_explicit_result(test_context); } - return 0; + +finish: + atomic_dec(&test_fix->nr_running); + return err; } /** @@ -405,13 +716,16 @@ exit: * * Release any resources that where 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_destroy_context(test_context); + kutf_context_put(test_context); return 0; } @@ -449,6 +763,7 @@ 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); @@ -498,13 +813,14 @@ static void kutf_remove_test_variant(struct kutf_test_fixture *test_fix) kfree(test_fix); } -void kutf_add_test_with_filters_and_data( +void kutf_add_test_with_filters_data_and_userdata( 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) + union kutf_callback_data test_data, + struct kutf_userdata_ops *userdata_ops) { struct kutf_test_function *test_func; struct dentry *tmp; @@ -557,6 +873,7 @@ void kutf_add_test_with_filters_and_data( 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; @@ -568,6 +885,27 @@ 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( @@ -827,6 +1165,11 @@ 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; + + kref_init(&new_context->kref); return new_context; @@ -836,13 +1179,27 @@ fail_alloc: return NULL; } -static void kutf_destroy_context(struct kutf_context *context) +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) { diff --git a/mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c b/mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c index ffd802f..c9cc444 100644 --- a/mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c +++ b/mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c @@ -232,10 +232,22 @@ int mali_kutf_irq_test_main_init(void) struct kutf_suite *suite; irq_app = kutf_create_application("irq"); + + if (NULL == irq_app) { + pr_warn("Creation of test application failed!\n"); + return -ENOMEM; + } + suite = kutf_create_suite(irq_app, "irq_default", 1, mali_kutf_irq_default_create_fixture, mali_kutf_irq_default_remove_fixture); + if (NULL == suite) { + pr_warn("Creation of test suite failed!\n"); + kutf_destroy_application(irq_app); + return -ENOMEM; + } + kutf_add_test(suite, 0x0, "irq_latency", mali_kutf_irq_latency); return 0; diff --git a/mali_kbase/tests/sconscript b/mali_kbase/tests/sconscript index 5337e10..0458411 100644 --- a/mali_kbase/tests/sconscript +++ b/mali_kbase/tests/sconscript @@ -34,4 +34,5 @@ if kutf_env['debug'] == '1': if env['unit'] == '1': SConscript('mali_kutf_ipa_test/sconscript') + SConscript('mali_kutf_ipa_unit_test/sconscript') SConscript('mali_kutf_vinstr_test/sconscript') |