diff options
Diffstat (limited to 'mali_kbase/tests/kutf')
-rw-r--r-- | mali_kbase/tests/kutf/kutf_helpers.c | 762 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_helpers_user.c | 116 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_resultset.c | 98 | ||||
-rw-r--r-- | mali_kbase/tests/kutf/kutf_suite.c | 596 |
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) */ |