summaryrefslogtreecommitdiff
path: root/mali_kbase/tests
diff options
context:
space:
mode:
authorSidath Senanayake <sidaths@google.com>2017-07-11 16:57:40 +0200
committerSidath Senanayake <sidaths@google.com>2017-07-11 16:57:40 +0200
commitea23e535ae857c92d45cb11bdd5dba7c27579726 (patch)
treee1bcda85e529f9be3f02202b81fb3e8f6ab73129 /mali_kbase/tests
parent6f5ab3baed824941f168ab133469f997d4450146 (diff)
downloadgpu-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.h216
-rw-r--r--mali_kbase/tests/include/kutf/kutf_helpers_user.h179
-rw-r--r--mali_kbase/tests/include/kutf/kutf_mem.h3
-rw-r--r--mali_kbase/tests/include/kutf/kutf_suite.h62
-rw-r--r--mali_kbase/tests/kutf/Kbuild2
-rw-r--r--mali_kbase/tests/kutf/kutf_helpers.c768
-rw-r--r--mali_kbase/tests/kutf/kutf_helpers_user.c462
-rw-r--r--mali_kbase/tests/kutf/kutf_mem.c10
-rw-r--r--mali_kbase/tests/kutf/kutf_suite.c381
-rw-r--r--mali_kbase/tests/mali_kutf_irq_test/mali_kutf_irq_test_main.c12
-rw-r--r--mali_kbase/tests/sconscript1
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')