summaryrefslogtreecommitdiff
path: root/mali_kbase/tests/kutf
diff options
context:
space:
mode:
Diffstat (limited to 'mali_kbase/tests/kutf')
-rw-r--r--mali_kbase/tests/kutf/kutf_helpers.c762
-rw-r--r--mali_kbase/tests/kutf/kutf_helpers_user.c116
-rw-r--r--mali_kbase/tests/kutf/kutf_resultset.c98
-rw-r--r--mali_kbase/tests/kutf/kutf_suite.c596
4 files changed, 408 insertions, 1164 deletions
diff --git a/mali_kbase/tests/kutf/kutf_helpers.c b/mali_kbase/tests/kutf/kutf_helpers.c
index 793d58c..bf887a5 100644
--- a/mali_kbase/tests/kutf/kutf_helpers.c
+++ b/mali_kbase/tests/kutf/kutf_helpers.c
@@ -18,8 +18,6 @@
/* 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>
@@ -27,742 +25,100 @@
#include <linux/wait.h>
#include <linux/uaccess.h>
+static DEFINE_SPINLOCK(kutf_input_lock);
-int kutf_helper_textbuf_init(struct kutf_helper_textbuf *textbuf,
- struct kutf_mempool *mempool, int max_line_size,
- int max_nr_lines)
+static bool pending_input(struct kutf_context *context)
{
- textbuf->scratchpad = kutf_mempool_alloc(mempool, max_line_size);
+ bool input_pending;
- 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;
- }
+ spin_lock(&kutf_input_lock);
- 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;
- }
- }
+ input_pending = !list_empty(&context->userdata.input_head);
-finish_noerr:
- ret = bytes_processed;
-finish:
- textbuf->prev_pos = *ppos;
-out_unlock:
- mutex_unlock(&textbuf->lock);
+ spin_unlock(&kutf_input_lock);
- return ret;
+ return input_pending;
}
-/**
- * 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)
+char *kutf_helper_input_dequeue(struct kutf_context *context, size_t *str_size)
{
- 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;
+ struct kutf_userdata_line *line;
- while (!line) {
- int needs_wake = 0;
+ spin_lock(&kutf_input_lock);
- 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;
- }
+ while (list_empty(&context->userdata.input_head)) {
+ int err;
- /* Update the start of the line pos for next time we're called */
- textbuf->prev_line_pos += line_start_pos;
+ kutf_set_waiting_for_input(context->result_set);
- /* 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;
- }
+ spin_unlock(&kutf_input_lock);
- /* 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)));
+ err = wait_event_interruptible(context->userdata.input_waitq,
+ pending_input(context));
- /* signals here are not restartable */
- if (ret)
- return ret;
- ret = mutex_lock_interruptible(&textbuf->lock);
- if (ret)
- return ret;
- }
+ if (err)
+ return ERR_PTR(-EINTR);
+ spin_lock(&kutf_input_lock);
}
-
- /* 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;
+ line = list_first_entry(&context->userdata.input_head,
+ struct kutf_userdata_line, node);
+ if (line->str) {
+ /*
+ * Unless it is the end-of-input marker,
+ * remove it from the list
+ */
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);
+ spin_unlock(&kutf_input_lock);
- return ret;
+ if (str_size)
+ *str_size = line->size;
+ return line->str;
}
-int kutf_helper_textbuf_wait_for_user(struct kutf_helper_textbuf *textbuf)
+int kutf_helper_input_enqueue(struct kutf_context *context,
+ const char __user *str, size_t size)
{
- 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;
+ struct kutf_userdata_line *line;
- 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);
+ line = kutf_mempool_alloc(&context->fixture_pool,
+ sizeof(*line) + size + 1);
+ if (!line)
+ return -ENOMEM;
+ if (str) {
+ unsigned long bytes_not_copied;
- now = jiffies;
+ line->size = size;
+ line->str = (void *)(line + 1);
+ bytes_not_copied = copy_from_user(line->str, str, size);
+ if (bytes_not_copied != 0)
+ return -EFAULT;
+ /* Zero terminate the string */
+ line->str[size] = '\0';
+ } else {
+ /* This is used to mark the end of input */
+ WARN_ON(size);
+ line->size = 0;
+ line->str = NULL;
}
- 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;
+ spin_lock(&kutf_input_lock);
- /* 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);
+ list_add_tail(&line->node, &context->userdata.input_head);
- while (list_empty(&textbuf->textbuf_list)) {
- int err;
+ kutf_clear_waiting_for_input(context->result_set);
- if (!textbuf->nr_user_clients) {
- /* No user-side clients - error */
- goto out;
- }
+ spin_unlock(&kutf_input_lock);
- /* 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);
- }
+ wake_up(&context->userdata.input_waitq);
- 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;
+ return 0;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_dequeue);
-int kutf_helper_textbuf_enqueue(struct kutf_helper_textbuf *textbuf,
- char *enqueue_str, int buf_max_size)
+void kutf_helper_input_enqueue_end_of_data(struct kutf_context *context)
{
- 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;
+ kutf_helper_input_enqueue(context, NULL, 0);
}
-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
index cf3b005..9e8ab99 100644
--- a/mali_kbase/tests/kutf/kutf_helpers_user.c
+++ b/mali_kbase/tests/kutf/kutf_helpers_user.c
@@ -17,6 +17,7 @@
/* Kernel UTF test helpers that mirror those for kutf-userside */
#include <kutf/kutf_helpers_user.h>
+#include <kutf/kutf_helpers.h>
#include <kutf/kutf_utils.h>
#include <linux/err.h>
@@ -48,7 +49,7 @@ static const char *get_val_type_name(enum kutf_helper_valtype valtype)
*
* - 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)
+static int validate_val_name(const char *val_str, int str_len)
{
int i = 0;
@@ -81,24 +82,44 @@ static int validate_val_name(char *val_str, int str_len)
*
* 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)
+static int find_quoted_string_valid_len(const char *str)
{
char *ptr;
const char *check_chars = "\\\n\"";
ptr = strpbrk(str, check_chars);
if (ptr)
- return ptr-str;
+ return (int)(ptr-str);
- return strlen(str);
+ return (int)strlen(str);
+}
+
+static int kutf_helper_userdata_enqueue(struct kutf_context *context,
+ const char *str)
+{
+ char *str_copy;
+ size_t len;
+ int err;
+
+ len = strlen(str)+1;
+
+ str_copy = kutf_mempool_alloc(&context->fixture_pool, len);
+ if (!str_copy)
+ return -ENOMEM;
+
+ strcpy(str_copy, str);
+
+ err = kutf_add_result(context, KUTF_RESULT_USERDATA, str_copy);
+
+ return err;
}
#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 kutf_helper_send_named_u64(struct kutf_context *context,
+ const char *val_name, u64 val)
{
int ret = 1;
char msgbuf[NAMED_U64_VAL_BUF_SZ];
@@ -117,9 +138,8 @@ int kutf_helper_textbuf_send_named_u64(struct kutf_context *context,
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);
+ ret = kutf_helper_userdata_enqueue(context, msgbuf);
if (ret) {
errmsg = kutf_dsprintf(&context->fixture_pool,
"Failed to send u64 value named '%s': send returned %d",
@@ -132,33 +152,31 @@ out_err:
kutf_test_fail(context, errmsg);
return ret;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_send_named_u64);
+EXPORT_SYMBOL(kutf_helper_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 kutf_helper_max_str_len_for_kern(const 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;
+ const int val_name_len = strlen(val_name);
+ const int start_delim_len = strlen(NAMED_STR_START_DELIM);
+ const int end_delim_len = strlen(NAMED_STR_END_DELIM);
+ int max_msg_len = kern_buf_sz;
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;
+ max_str_len = max_msg_len - val_name_len - start_delim_len -
+ end_delim_len;
return max_str_len;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_max_str_len_for_kern);
+EXPORT_SYMBOL(kutf_helper_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 kutf_helper_send_named_str(struct kutf_context *context,
+ const char *val_name,
+ const char *val_str)
{
int val_str_len;
int str_buf_sz;
@@ -215,7 +233,7 @@ int kutf_helper_textbuf_send_named_str(struct kutf_context *context,
/* Terminator */
*copy_ptr = '\0';
- ret = kutf_helper_textbuf_enqueue(textbuf, str_buf, str_buf_sz);
+ ret = kutf_helper_userdata_enqueue(context, str_buf);
if (ret) {
errmsg = kutf_dsprintf(&context->fixture_pool,
@@ -232,12 +250,13 @@ out_err:
kfree(str_buf);
return ret;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_send_named_str);
+EXPORT_SYMBOL(kutf_helper_send_named_str);
-int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_val,
- struct kutf_helper_textbuf *textbuf)
+int kutf_helper_receive_named_val(
+ struct kutf_context *context,
+ struct kutf_helper_named_val *named_val)
{
- int recv_sz;
+ size_t recv_sz;
char *recv_str;
char *search_ptr;
char *name_str = NULL;
@@ -246,15 +265,13 @@ int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_va
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);
+ recv_str = kutf_helper_input_dequeue(context, &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]);
@@ -271,7 +288,8 @@ int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_va
}
}
if (!name_str) {
- pr_err("Invalid name part for recevied string '%s'\n", recv_str);
+ pr_err("Invalid name part for received string '%s'\n",
+ recv_str);
return KUTF_HELPER_ERR_INVALID_NAME;
}
@@ -299,24 +317,6 @@ int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_va
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;
@@ -357,8 +357,8 @@ int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_va
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 */
+ pr_err("Unreachable, fix kutf_helper_receive_named_val\n");
+ /* Coding error, report as though 'run' file failed */
return -EINVAL;
}
@@ -367,16 +367,18 @@ int kutf_helper_textbuf_receive_named_val(struct kutf_helper_named_val *named_va
return KUTF_HELPER_ERR_NONE;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_receive_named_val);
+EXPORT_SYMBOL(kutf_helper_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 kutf_helper_receive_check_val(
+ struct kutf_helper_named_val *named_val,
+ struct kutf_context *context,
+ const char *expect_val_name,
+ enum kutf_helper_valtype expect_val_type)
{
int err;
- err = kutf_helper_textbuf_receive_named_val(named_val, textbuf);
+ err = kutf_helper_receive_named_val(context, named_val);
if (err < 0) {
const char *msg = kutf_dsprintf(&context->fixture_pool,
"Failed to receive value named '%s'",
@@ -438,7 +440,7 @@ out_fail_and_fixup:
/* But at least allow the caller to continue in the test with failures */
return 0;
}
-EXPORT_SYMBOL(kutf_helper_textbuf_receive_check_val);
+EXPORT_SYMBOL(kutf_helper_receive_check_val);
void kutf_helper_output_named_val(struct kutf_helper_named_val *named_val)
{
diff --git a/mali_kbase/tests/kutf/kutf_resultset.c b/mali_kbase/tests/kutf/kutf_resultset.c
index 5bd0496..41645a4 100644
--- a/mali_kbase/tests/kutf/kutf_resultset.c
+++ b/mali_kbase/tests/kutf/kutf_resultset.c
@@ -20,16 +20,15 @@
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <kutf/kutf_suite.h>
#include <kutf/kutf_resultset.h>
-/**
- * struct kutf_result_set - Represents a set of results.
- * @results: Pointer to the linked list where the results are stored.
- */
-struct kutf_result_set {
- struct list_head results;
-};
+/* Lock to protect all result structures */
+static DEFINE_SPINLOCK(kutf_result_lock);
struct kutf_result_set *kutf_create_result_set(void)
{
@@ -42,6 +41,8 @@ struct kutf_result_set *kutf_create_result_set(void)
}
INIT_LIST_HEAD(&set->results);
+ init_waitqueue_head(&set->waitq);
+ set->flags = 0;
return set;
@@ -49,11 +50,12 @@ fail_alloc:
return NULL;
}
-void kutf_add_result(struct kutf_mempool *mempool,
- struct kutf_result_set *set,
+int kutf_add_result(struct kutf_context *context,
enum kutf_result_status status,
const char *message)
{
+ struct kutf_mempool *mempool = &context->fixture_pool;
+ struct kutf_result_set *set = context->result_set;
/* Create the new result */
struct kutf_result *new_result;
@@ -62,14 +64,22 @@ void kutf_add_result(struct kutf_mempool *mempool,
new_result = kutf_mempool_alloc(mempool, sizeof(*new_result));
if (!new_result) {
pr_err("Result allocation failed\n");
- return;
+ return -ENOMEM;
}
INIT_LIST_HEAD(&new_result->node);
new_result->status = status;
new_result->message = message;
+ spin_lock(&kutf_result_lock);
+
list_add_tail(&new_result->node, &set->results);
+
+ spin_unlock(&kutf_result_lock);
+
+ wake_up(&set->waitq);
+
+ return 0;
}
void kutf_destroy_result_set(struct kutf_result_set *set)
@@ -80,16 +90,70 @@ void kutf_destroy_result_set(struct kutf_result_set *set)
kfree(set);
}
+static bool kutf_has_result(struct kutf_result_set *set)
+{
+ bool has_result;
+
+ spin_lock(&kutf_result_lock);
+ if (set->flags & KUTF_RESULT_SET_WAITING_FOR_INPUT)
+ /* Pretend there are results if waiting for input */
+ has_result = true;
+ else
+ has_result = !list_empty(&set->results);
+ spin_unlock(&kutf_result_lock);
+
+ return has_result;
+}
+
struct kutf_result *kutf_remove_result(struct kutf_result_set *set)
{
- if (!list_empty(&set->results)) {
- struct kutf_result *ret;
+ struct kutf_result *result = NULL;
+ int ret;
+
+ do {
+ ret = wait_event_interruptible(set->waitq,
+ kutf_has_result(set));
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ spin_lock(&kutf_result_lock);
+
+ if (!list_empty(&set->results)) {
+ result = list_first_entry(&set->results,
+ struct kutf_result,
+ node);
+ list_del(&result->node);
+ } else if (set->flags & KUTF_RESULT_SET_WAITING_FOR_INPUT) {
+ /* Return a fake result */
+ static struct kutf_result waiting = {
+ .status = KUTF_RESULT_USERDATA_WAIT
+ };
+ result = &waiting;
+ }
+ /* If result == NULL then there was a race with the event
+ * being removed between the check in kutf_has_result and
+ * the lock being obtained. In this case we retry
+ */
+
+ spin_unlock(&kutf_result_lock);
+ } while (result == NULL);
+
+ return result;
+}
- ret = list_first_entry(&set->results, struct kutf_result, node);
- list_del(&ret->node);
- return ret;
- }
+void kutf_set_waiting_for_input(struct kutf_result_set *set)
+{
+ spin_lock(&kutf_result_lock);
+ set->flags |= KUTF_RESULT_SET_WAITING_FOR_INPUT;
+ spin_unlock(&kutf_result_lock);
- return NULL;
+ wake_up(&set->waitq);
}
+void kutf_clear_waiting_for_input(struct kutf_result_set *set)
+{
+ spin_lock(&kutf_result_lock);
+ set->flags &= ~KUTF_RESULT_SET_WAITING_FOR_INPUT;
+ spin_unlock(&kutf_result_lock);
+}
diff --git a/mali_kbase/tests/kutf/kutf_suite.c b/mali_kbase/tests/kutf/kutf_suite.c
index ad30cc8..4968f24 100644
--- a/mali_kbase/tests/kutf/kutf_suite.c
+++ b/mali_kbase/tests/kutf/kutf_suite.c
@@ -27,12 +27,14 @@
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/atomic.h>
+#include <linux/sched.h>
#include <generated/autoconf.h>
#include <kutf/kutf_suite.h>
#include <kutf/kutf_resultset.h>
#include <kutf/kutf_utils.h>
+#include <kutf/kutf_helpers.h>
#if defined(CONFIG_DEBUG_FS)
@@ -61,8 +63,6 @@ struct kutf_application {
* @variant_list: List head to store all the variants which can run on
* this function
* @dir: debugfs directory for this test function
- * @userdata_ops: Callbacks to use for sending and receiving data to
- * userspace.
*/
struct kutf_test_function {
struct kutf_suite *suite;
@@ -73,7 +73,6 @@ struct kutf_test_function {
struct list_head node;
struct list_head variant_list;
struct dentry *dir;
- struct kutf_userdata_ops userdata_ops;
};
/**
@@ -83,17 +82,16 @@ struct kutf_test_function {
* @fixture_index: Index of this fixture
* @node: List node for variant_list
* @dir: debugfs directory for this test fixture
- * @nr_running: Current count of user-clients running this fixture
*/
struct kutf_test_fixture {
struct kutf_test_function *test_func;
unsigned int fixture_index;
struct list_head node;
struct dentry *dir;
- atomic_t nr_running;
};
-struct dentry *base_dir;
+static struct dentry *base_dir;
+static struct workqueue_struct *kutf_workq;
/**
* struct kutf_convert_table - Structure which keeps test results
@@ -252,263 +250,6 @@ static const struct file_operations kutf_debugfs_const_string_ops = {
};
/**
- * kutf_debugfs_data_open() Debugfs open callback for the "data" entry.
- * @inode: inode of the opened file
- * @file: Opened file to read from
- *
- * This function notifies the userdata callbacks that the userdata file has
- * been opened, for tracking purposes.
- *
- * It is called on both the context's userdata_consumer_priv and
- * userdata_producer_priv.
- *
- * This takes a refcount on the kutf_context
- *
- * Return: 0 on success
- */
-static int kutf_debugfs_data_open(struct inode *inode, struct file *file)
-{
- struct kutf_context *test_context = inode->i_private;
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
- int err;
-
- simple_open(inode, file);
-
- /* This is not an error */
- if (!test_func->userdata_ops.open)
- goto out_no_ops;
-
- /* This is safe here - the 'data' file is only openable whilst the
- * initial refcount is still present, and the initial refcount is only
- * dropped strictly after the 'data' file is removed */
- kutf_context_get(test_context);
-
- if (test_context->userdata_consumer_priv) {
- err = test_func->userdata_ops.open(test_context->userdata_consumer_priv);
- if (err)
- goto out_consumer_fail;
- }
-
- if (test_context->userdata_producer_priv) {
- err = test_func->userdata_ops.open(test_context->userdata_producer_priv);
- if (err)
- goto out_producer_fail;
- }
-
-out_no_ops:
- return 0;
-
-out_producer_fail:
- if (test_func->userdata_ops.release && test_context->userdata_consumer_priv)
- test_func->userdata_ops.release(test_context->userdata_consumer_priv);
-out_consumer_fail:
- kutf_context_put(test_context);
-
- return err;
-}
-
-
-/**
- * kutf_debugfs_data_read() Debugfs read callback for the "data" entry.
- * @file: Opened file to read from
- * @buf: User buffer to write the data into
- * @len: Amount of data to read
- * @ppos: Offset into file to read from
- *
- * This function allows user and kernel to exchange extra data necessary for
- * the test fixture.
- *
- * The data is read from the first struct kutf_context running the fixture
- *
- * Return: Number of bytes read
- */
-static ssize_t kutf_debugfs_data_read(struct file *file, char __user *buf,
- size_t len, loff_t *ppos)
-{
- struct kutf_context *test_context = file->private_data;
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
- ssize_t (*producer)(void *private, char __user *userbuf,
- size_t userbuf_len, loff_t *ppos);
- ssize_t count;
-
- producer = test_func->userdata_ops.producer;
- /* Can only read if there's a producer callback */
- if (!producer)
- return -ENODEV;
-
- count = producer(test_context->userdata_producer_priv, buf, len, ppos);
-
- return count;
-}
-
-/**
- * kutf_debugfs_data_write() Debugfs write callback for the "data" entry.
- * @file: Opened file to write to
- * @buf: User buffer to read the data from
- * @len: Amount of data to write
- * @ppos: Offset into file to write to
- *
- * This function allows user and kernel to exchange extra data necessary for
- * the test fixture.
- *
- * The data is added to the first struct kutf_context running the fixture
- *
- * Return: Number of bytes written
- */
-static ssize_t kutf_debugfs_data_write(struct file *file,
- const char __user *buf, size_t len, loff_t *ppos)
-{
- struct kutf_context *test_context = file->private_data;
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
- ssize_t (*consumer)(void *private, const char __user *userbuf,
- size_t userbuf_len, loff_t *ppos);
- ssize_t count;
-
- consumer = test_func->userdata_ops.consumer;
- /* Can only write if there's a consumer callback */
- if (!consumer)
- return -ENODEV;
-
- count = consumer(test_context->userdata_consumer_priv, buf, len, ppos);
-
- return count;
-}
-
-
-/**
- * kutf_debugfs_data_release() - Debugfs release callback for the "data" entry.
- * @inode: File entry representation
- * @file: A specific opening of the file
- *
- * This function notifies the userdata callbacks that the userdata file has
- * been closed, for tracking purposes.
- *
- * It is called on both the context's userdata_consumer_priv and
- * userdata_producer_priv.
- *
- * It also drops the refcount on the kutf_context that was taken during
- * kutf_debugfs_data_open()
- */
-static int kutf_debugfs_data_release(struct inode *inode, struct file *file)
-{
- struct kutf_context *test_context = file->private_data;
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
-
- if (!test_func->userdata_ops.release)
- return 0;
-
- if (test_context->userdata_consumer_priv)
- test_func->userdata_ops.release(test_context->userdata_consumer_priv);
- if (test_context->userdata_producer_priv)
- test_func->userdata_ops.release(test_context->userdata_producer_priv);
-
- kutf_context_put(test_context);
-
- return 0;
-}
-
-
-static const struct file_operations kutf_debugfs_data_ops = {
- .owner = THIS_MODULE,
- .open = kutf_debugfs_data_open,
- .read = kutf_debugfs_data_read,
- .write = kutf_debugfs_data_write,
- .release = kutf_debugfs_data_release,
- .llseek = default_llseek,
-};
-
-/**
- * userdata_init() - Initialize userspace data exchange for a test, if
- * specified by that test
- * @test_context: Test context
- *
- * Note that this allows new refcounts to be made on test_context by userspace
- * threads opening the 'data' file.
- *
- * Return: 0 on success, negative value corresponding to error code in failure
- * and kutf result will be set appropriately to indicate the error
- */
-static int userdata_init(struct kutf_context *test_context)
-{
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
- int err = 0;
- struct dentry *userdata_dentry;
-
- /* Valid to have neither a producer or consumer, which is the case for
- * tests not requiring usersdata */
- if ((!test_func->userdata_ops.consumer) && (!test_func->userdata_ops.producer))
- return err;
-
- if (test_func->userdata_ops.consumer && !test_context->userdata_consumer_priv) {
- kutf_test_fatal(test_context,
- "incorrect test setup - userdata consumer provided without private data");
- return -EFAULT;
- }
-
- if (test_func->userdata_ops.producer && !test_context->userdata_producer_priv) {
- kutf_test_fatal(test_context,
- "incorrect test setup - userdata producer provided without private data");
- return -EFAULT;
- }
-
- userdata_dentry = debugfs_create_file("data", S_IROTH, test_fix->dir,
- test_context, &kutf_debugfs_data_ops);
-
- if (!userdata_dentry) {
- pr_err("Failed to create debugfs file \"data\" when running fixture\n");
- /* Not using Fatal (which stops other tests running),
- * nor Abort (which indicates teardown should not be done) */
- kutf_test_fail(test_context,
- "failed to create 'data' file for userside data exchange");
-
- /* Error code is discarded by caller, but consistent with other
- * debugfs_create_file failures */
- err = -EEXIST;
- } else {
- test_context->userdata_dentry = userdata_dentry;
- }
-
-
- return err;
-}
-
-/**
- * userdata_term() - Terminate userspace data exchange for a test, if specified
- * by that test
- * @test_context: Test context
- *
- * Note This also prevents new refcounts being made on @test_context by userspace
- * threads opening the 'data' file for this test. Any existing open file descriptors
- * to the 'data' file will still be safe to use by userspace.
- */
-static void userdata_term(struct kutf_context *test_context)
-{
- struct kutf_test_fixture *test_fix = test_context->test_fix;
- struct kutf_test_function *test_func = test_fix->test_func;
- void (*notify_ended)(void *priv) = test_func->userdata_ops.notify_ended;
-
- /* debugfs_remove() is safe when parameter is error or NULL */
- debugfs_remove(test_context->userdata_dentry);
-
- /* debugfs_remove() doesn't kill any currently open file descriptors on
- * this file, and such fds are still safe to use providing test_context
- * is properly refcounted */
-
- if (notify_ended) {
- if (test_context->userdata_consumer_priv)
- notify_ended(test_context->userdata_consumer_priv);
- if (test_context->userdata_producer_priv)
- notify_ended(test_context->userdata_producer_priv);
- }
-
-}
-
-/**
* kutf_add_explicit_result() - Check if an explicit result needs to be added
* @context: KUTF test context
*/
@@ -563,75 +304,75 @@ static void kutf_add_explicit_result(struct kutf_context *context)
}
}
+static void kutf_run_test(struct work_struct *data)
+{
+ struct kutf_context *test_context = container_of(data,
+ struct kutf_context, work);
+ struct kutf_suite *suite = test_context->suite;
+ struct kutf_test_function *test_func;
+
+ test_func = test_context->test_fix->test_func;
+
+ /*
+ * Call the create fixture function if required before the
+ * fixture is run
+ */
+ if (suite->create_fixture)
+ test_context->fixture = suite->create_fixture(test_context);
+
+ /* Only run the test if the fixture was created (if required) */
+ if ((suite->create_fixture && test_context->fixture) ||
+ (!suite->create_fixture)) {
+ /* Run this fixture */
+ test_func->execute(test_context);
+
+ if (suite->remove_fixture)
+ suite->remove_fixture(test_context);
+
+ kutf_add_explicit_result(test_context);
+ }
+
+ kutf_add_result(test_context, KUTF_RESULT_TEST_FINISHED, NULL);
+
+ kutf_context_put(test_context);
+}
+
/**
* kutf_debugfs_run_open() Debugfs open callback for the "run" entry.
* @inode: inode of the opened file
* @file: Opened file to read from
*
- * This function retrieves the test fixture data that is associated with the
- * opened file and works back to get the test, suite and application so
- * it can then run the test that is associated with the file entry.
+ * This function creates a KUTF context and queues it onto a workqueue to be
+ * run asynchronously. The resulting file descriptor can be used to communicate
+ * userdata to the test and to read back the results of the test execution.
*
* Return: 0 on success
*/
static int kutf_debugfs_run_open(struct inode *inode, struct file *file)
{
struct kutf_test_fixture *test_fix = inode->i_private;
- struct kutf_test_function *test_func = test_fix->test_func;
- struct kutf_suite *suite = test_func->suite;
struct kutf_context *test_context;
int err = 0;
- /* For the moment, only one user-client should be attempting to run
- * this at a time. This simplifies how we lookup the kutf_context when
- * using the 'data' file.
- * Removing this restriction would require a rewrite of the mechanism
- * of the 'data' file to pass data in, perhaps 'data' created here and
- * based upon userspace thread's pid */
- if (atomic_inc_return(&test_fix->nr_running) != 1) {
- err = -EBUSY;
- goto finish;
- }
-
test_context = kutf_create_context(test_fix);
if (!test_context) {
- err = -ENODEV;
+ err = -ENOMEM;
goto finish;
}
file->private_data = test_context;
- /*
- * Call the create fixture function if required before the
- * fixture is run
- */
- if (suite->create_fixture)
- test_context->fixture = suite->create_fixture(test_context);
-
- /* Only run the test if the fixture was created (if required) */
- if ((suite->create_fixture && test_context->fixture) ||
- (!suite->create_fixture)) {
- int late_err;
- /* Setup any userdata exchange */
- late_err = userdata_init(test_context);
-
- if (!late_err)
- /* Run this fixture */
- test_func->execute(test_context);
-
- userdata_term(test_context);
-
- if (suite->remove_fixture)
- suite->remove_fixture(test_context);
+ /* This reference is release by the kutf_run_test */
+ kutf_context_get(test_context);
- kutf_add_explicit_result(test_context);
- }
+ queue_work(kutf_workq, &test_context->work);
finish:
- atomic_dec(&test_fix->nr_running);
return err;
}
+#define USERDATA_WARNING_MESSAGE "WARNING: This test requires userdata\n"
+
/**
* kutf_debugfs_run_read() - Debugfs read callback for the "run" entry.
* @file: Opened file to read from
@@ -639,8 +380,14 @@ finish:
* @len: Amount of data to read
* @ppos: Offset into file to read from
*
- * This function emits the results which where logged during the opening of
- * the file kutf_debugfs_run_open.
+ * This function emits the results of the test, blocking until they are
+ * available.
+ *
+ * If the test involves user data then this will also return user data records
+ * to user space. If the test is waiting for user data then this function will
+ * output a message (to make the likes of 'cat' display it), followed by
+ * returning 0 to mark the end of file.
+ *
* Results will be emitted one at a time, once all the results have been read
* 0 will be returned to indicate there is no more data.
*
@@ -653,68 +400,153 @@ static ssize_t kutf_debugfs_run_read(struct file *file, char __user *buf,
struct kutf_result *res;
unsigned long bytes_not_copied;
ssize_t bytes_copied = 0;
+ char *kutf_str_ptr = NULL;
+ size_t kutf_str_len = 0;
+ size_t message_len = 0;
+ char separator = ':';
+ char terminator = '\n';
- /* Note: This code assumes a result is read completely */
res = kutf_remove_result(test_context->result_set);
- if (res) {
- char *kutf_str_ptr = NULL;
- unsigned int kutf_str_len = 0;
- unsigned int message_len = 0;
- char separator = ':';
- char terminator = '\n';
-
- kutf_result_to_string(&kutf_str_ptr, res->status);
- if (kutf_str_ptr)
- kutf_str_len = strlen(kutf_str_ptr);
-
- if (res->message)
- message_len = strlen(res->message);
-
- if ((kutf_str_len + 1 + message_len + 1) > len) {
- pr_err("Not enough space in user buffer for a single result");
+
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+
+ /*
+ * Handle 'fake' results - these results are converted to another
+ * form before being returned from the kernel
+ */
+ switch (res->status) {
+ case KUTF_RESULT_TEST_FINISHED:
+ return 0;
+ case KUTF_RESULT_USERDATA_WAIT:
+ if (test_context->userdata.flags &
+ KUTF_USERDATA_WARNING_OUTPUT) {
+ /*
+ * Warning message already output,
+ * signal end-of-file
+ */
return 0;
}
- /* First copy the result string */
- if (kutf_str_ptr) {
- bytes_not_copied = copy_to_user(&buf[0], kutf_str_ptr,
- kutf_str_len);
- bytes_copied += kutf_str_len - bytes_not_copied;
- if (bytes_not_copied)
- goto exit;
+ message_len = sizeof(USERDATA_WARNING_MESSAGE)-1;
+ if (message_len > len)
+ message_len = len;
+
+ bytes_not_copied = copy_to_user(buf,
+ USERDATA_WARNING_MESSAGE,
+ message_len);
+ if (bytes_not_copied != 0)
+ return -EFAULT;
+ test_context->userdata.flags |= KUTF_USERDATA_WARNING_OUTPUT;
+ return message_len;
+ case KUTF_RESULT_USERDATA:
+ message_len = strlen(res->message);
+ if (message_len > len-1) {
+ message_len = len-1;
+ pr_warn("User data truncated, read not long enough\n");
+ }
+ bytes_not_copied = copy_to_user(buf, res->message,
+ message_len);
+ if (bytes_not_copied != 0) {
+ pr_warn("Failed to copy data to user space buffer\n");
+ return -EFAULT;
+ }
+ /* Finally the terminator */
+ bytes_not_copied = copy_to_user(&buf[message_len],
+ &terminator, 1);
+ if (bytes_not_copied != 0) {
+ pr_warn("Failed to copy data to user space buffer\n");
+ return -EFAULT;
}
+ return message_len+1;
+ default:
+ /* Fall through - this is a test result */
+ break;
+ }
- /* Then the separator */
- bytes_not_copied = copy_to_user(&buf[bytes_copied],
- &separator, 1);
- bytes_copied += 1 - bytes_not_copied;
+ /* Note: This code assumes a result is read completely */
+ kutf_result_to_string(&kutf_str_ptr, res->status);
+ if (kutf_str_ptr)
+ kutf_str_len = strlen(kutf_str_ptr);
+
+ if (res->message)
+ message_len = strlen(res->message);
+
+ if ((kutf_str_len + 1 + message_len + 1) > len) {
+ pr_err("Not enough space in user buffer for a single result");
+ return 0;
+ }
+
+ /* First copy the result string */
+ if (kutf_str_ptr) {
+ bytes_not_copied = copy_to_user(&buf[0], kutf_str_ptr,
+ kutf_str_len);
+ bytes_copied += kutf_str_len - bytes_not_copied;
if (bytes_not_copied)
goto exit;
+ }
- /* Finally Next copy the result string */
- if (res->message) {
- bytes_not_copied = copy_to_user(&buf[bytes_copied],
- res->message, message_len);
- bytes_copied += message_len - bytes_not_copied;
- if (bytes_not_copied)
- goto exit;
- }
+ /* Then the separator */
+ bytes_not_copied = copy_to_user(&buf[bytes_copied],
+ &separator, 1);
+ bytes_copied += 1 - bytes_not_copied;
+ if (bytes_not_copied)
+ goto exit;
- /* Finally the terminator */
+ /* Finally Next copy the result string */
+ if (res->message) {
bytes_not_copied = copy_to_user(&buf[bytes_copied],
- &terminator, 1);
- bytes_copied += 1 - bytes_not_copied;
+ res->message, message_len);
+ bytes_copied += message_len - bytes_not_copied;
+ if (bytes_not_copied)
+ goto exit;
}
+
+ /* Finally the terminator */
+ bytes_not_copied = copy_to_user(&buf[bytes_copied],
+ &terminator, 1);
+ bytes_copied += 1 - bytes_not_copied;
+
exit:
return bytes_copied;
}
/**
+ * kutf_debugfs_run_write() Debugfs write callback for the "run" entry.
+ * @file: Opened file to write to
+ * @buf: User buffer to read the data from
+ * @len: Amount of data to write
+ * @ppos: Offset into file to write to
+ *
+ * This function allows user and kernel to exchange extra data necessary for
+ * the test fixture.
+ *
+ * The data is added to the first struct kutf_context running the fixture
+ *
+ * Return: Number of bytes written
+ */
+static ssize_t kutf_debugfs_run_write(struct file *file,
+ const char __user *buf, size_t len, loff_t *ppos)
+{
+ int ret = 0;
+ struct kutf_context *test_context = file->private_data;
+
+ if (len > KUTF_MAX_LINE_LENGTH)
+ return -EINVAL;
+
+ ret = kutf_helper_input_enqueue(test_context, buf, len);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+
+/**
* kutf_debugfs_run_release() - Debugfs release callback for the "run" entry.
* @inode: File entry representation
* @file: A specific opening of the file
*
- * Release any resources that where created during the opening of the file
+ * Release any resources that were created during the opening of the file
*
* Note that resources may not be released immediately, that might only happen
* later when other users of the kutf_context release their refcount.
@@ -725,6 +557,8 @@ static int kutf_debugfs_run_release(struct inode *inode, struct file *file)
{
struct kutf_context *test_context = file->private_data;
+ kutf_helper_input_enqueue_end_of_data(test_context);
+
kutf_context_put(test_context);
return 0;
}
@@ -733,6 +567,7 @@ static const struct file_operations kutf_debugfs_run_ops = {
.owner = THIS_MODULE,
.open = kutf_debugfs_run_open,
.read = kutf_debugfs_run_read,
+ .write = kutf_debugfs_run_write,
.release = kutf_debugfs_run_release,
.llseek = default_llseek,
};
@@ -763,7 +598,6 @@ static int create_fixture_variant(struct kutf_test_function *test_func,
test_fix->test_func = test_func;
test_fix->fixture_index = fixture_index;
- atomic_set(&test_fix->nr_running, 0);
snprintf(name, sizeof(name), "%d", fixture_index);
test_fix->dir = debugfs_create_dir(name, test_func->dir);
@@ -783,8 +617,14 @@ static int create_fixture_variant(struct kutf_test_function *test_func,
goto fail_file;
}
- tmp = debugfs_create_file("run", S_IROTH, test_fix->dir, test_fix,
- &kutf_debugfs_run_ops);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0)
+ tmp = debugfs_create_file_unsafe(
+#else
+ tmp = debugfs_create_file(
+#endif
+ "run", 0600, test_fix->dir,
+ test_fix,
+ &kutf_debugfs_run_ops);
if (!tmp) {
pr_err("Failed to create debugfs file \"run\" when adding fixture\n");
/* Might not be the right error, we don't get it passed back to us */
@@ -813,14 +653,13 @@ static void kutf_remove_test_variant(struct kutf_test_fixture *test_fix)
kfree(test_fix);
}
-void kutf_add_test_with_filters_data_and_userdata(
+void kutf_add_test_with_filters_and_data(
struct kutf_suite *suite,
unsigned int id,
const char *name,
void (*execute)(struct kutf_context *context),
unsigned int filters,
- union kutf_callback_data test_data,
- struct kutf_userdata_ops *userdata_ops)
+ union kutf_callback_data test_data)
{
struct kutf_test_function *test_func;
struct dentry *tmp;
@@ -873,7 +712,6 @@ void kutf_add_test_with_filters_data_and_userdata(
test_func->suite = suite;
test_func->execute = execute;
test_func->test_data = test_data;
- memcpy(&test_func->userdata_ops, userdata_ops, sizeof(*userdata_ops));
list_add(&test_func->node, &suite->test_list);
return;
@@ -885,27 +723,6 @@ fail_dir:
fail_alloc:
return;
}
-EXPORT_SYMBOL(kutf_add_test_with_filters_data_and_userdata);
-
-void kutf_add_test_with_filters_and_data(
- struct kutf_suite *suite,
- unsigned int id,
- const char *name,
- void (*execute)(struct kutf_context *context),
- unsigned int filters,
- union kutf_callback_data test_data)
-{
- struct kutf_userdata_ops userdata_ops = {
- .open = NULL,
- .release = NULL,
- .consumer = NULL,
- .producer = NULL,
- };
-
- kutf_add_test_with_filters_data_and_userdata(suite, id, name, execute,
- filters, test_data, &userdata_ops);
-}
-
EXPORT_SYMBOL(kutf_add_test_with_filters_and_data);
void kutf_add_test_with_filters(
@@ -1150,7 +967,7 @@ static struct kutf_context *kutf_create_context(
new_context->result_set = kutf_create_result_set();
if (!new_context->result_set) {
- pr_err("Failed to create resultset");
+ pr_err("Failed to create result set");
goto fail_result_set;
}
@@ -1165,9 +982,12 @@ static struct kutf_context *kutf_create_context(
new_context->fixture_index = test_fix->fixture_index;
new_context->fixture_name = NULL;
new_context->test_data = test_fix->test_func->test_data;
- new_context->userdata_consumer_priv = NULL;
- new_context->userdata_producer_priv = NULL;
- new_context->userdata_dentry = NULL;
+
+ new_context->userdata.flags = 0;
+ INIT_LIST_HEAD(&new_context->userdata.input_head);
+ init_waitqueue_head(&new_context->userdata.input_waitq);
+
+ INIT_WORK(&new_context->work, kutf_run_test);
kref_init(&new_context->kref);
@@ -1227,8 +1047,7 @@ static void kutf_test_log_result(
context->status = new_status;
if (context->expected_status != new_status)
- kutf_add_result(&context->fixture_pool, context->result_set,
- new_status, message);
+ kutf_add_result(context, new_status, message);
}
void kutf_test_log_result_external(
@@ -1344,18 +1163,18 @@ EXPORT_SYMBOL(kutf_test_abort);
*/
static int __init init_kutf_core(void)
{
- int ret;
+ kutf_workq = alloc_workqueue("kutf workq", WQ_UNBOUND, 1);
+ if (!kutf_workq)
+ return -ENOMEM;
base_dir = debugfs_create_dir("kutf_tests", NULL);
if (!base_dir) {
- ret = -ENODEV;
- goto exit_dir;
+ destroy_workqueue(kutf_workq);
+ kutf_workq = NULL;
+ return -ENOMEM;
}
return 0;
-
-exit_dir:
- return ret;
}
/**
@@ -1366,6 +1185,9 @@ exit_dir:
static void __exit exit_kutf_core(void)
{
debugfs_remove_recursive(base_dir);
+
+ if (kutf_workq)
+ destroy_workqueue(kutf_workq);
}
#else /* defined(CONFIG_DEBUG_FS) */