|
@@ -86,6 +86,39 @@ MODULE_PARM_DESC(timeout, "Transfer Timeout in msec (default: 3000), "
|
|
|
#define PATTERN_OVERWRITE 0x20
|
|
|
#define PATTERN_COUNT_MASK 0x1f
|
|
|
|
|
|
+enum dmatest_error_type {
|
|
|
+ DMATEST_ET_OK,
|
|
|
+ DMATEST_ET_MAP_SRC,
|
|
|
+ DMATEST_ET_MAP_DST,
|
|
|
+ DMATEST_ET_PREP,
|
|
|
+ DMATEST_ET_SUBMIT,
|
|
|
+ DMATEST_ET_TIMEOUT,
|
|
|
+ DMATEST_ET_DMA_ERROR,
|
|
|
+ DMATEST_ET_DMA_IN_PROGRESS,
|
|
|
+ DMATEST_ET_VERIFY,
|
|
|
+};
|
|
|
+
|
|
|
+struct dmatest_thread_result {
|
|
|
+ struct list_head node;
|
|
|
+ unsigned int n;
|
|
|
+ unsigned int src_off;
|
|
|
+ unsigned int dst_off;
|
|
|
+ unsigned int len;
|
|
|
+ enum dmatest_error_type type;
|
|
|
+ union {
|
|
|
+ unsigned long data;
|
|
|
+ dma_cookie_t cookie;
|
|
|
+ enum dma_status status;
|
|
|
+ int error;
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+struct dmatest_result {
|
|
|
+ struct list_head node;
|
|
|
+ char *name;
|
|
|
+ struct list_head results;
|
|
|
+};
|
|
|
+
|
|
|
struct dmatest_info;
|
|
|
|
|
|
struct dmatest_thread {
|
|
@@ -146,6 +179,10 @@ struct dmatest_info {
|
|
|
/* debugfs related stuff */
|
|
|
struct dentry *root;
|
|
|
struct dmatest_params dbgfs_params;
|
|
|
+
|
|
|
+ /* Test results */
|
|
|
+ struct list_head results;
|
|
|
+ struct mutex results_lock;
|
|
|
};
|
|
|
|
|
|
static struct dmatest_info test_info;
|
|
@@ -303,6 +340,98 @@ static unsigned int min_odd(unsigned int x, unsigned int y)
|
|
|
return val % 2 ? val : val - 1;
|
|
|
}
|
|
|
|
|
|
+static char *thread_result_get(const char *name,
|
|
|
+ struct dmatest_thread_result *tr)
|
|
|
+{
|
|
|
+ static const char * const messages[] = {
|
|
|
+ [DMATEST_ET_OK] = "No errors",
|
|
|
+ [DMATEST_ET_MAP_SRC] = "src mapping error",
|
|
|
+ [DMATEST_ET_MAP_DST] = "dst mapping error",
|
|
|
+ [DMATEST_ET_PREP] = "prep error",
|
|
|
+ [DMATEST_ET_SUBMIT] = "submit error",
|
|
|
+ [DMATEST_ET_TIMEOUT] = "test timed out",
|
|
|
+ [DMATEST_ET_DMA_ERROR] =
|
|
|
+ "got completion callback (DMA_ERROR)",
|
|
|
+ [DMATEST_ET_DMA_IN_PROGRESS] =
|
|
|
+ "got completion callback (DMA_IN_PROGRESS)",
|
|
|
+ [DMATEST_ET_VERIFY] = "errors",
|
|
|
+ };
|
|
|
+ static char buf[512];
|
|
|
+
|
|
|
+ snprintf(buf, sizeof(buf) - 1,
|
|
|
+ "%s: #%u: %s with src_off=0x%x ""dst_off=0x%x len=0x%x (%lu)",
|
|
|
+ name, tr->n, messages[tr->type], tr->src_off, tr->dst_off,
|
|
|
+ tr->len, tr->data);
|
|
|
+
|
|
|
+ return buf;
|
|
|
+}
|
|
|
+
|
|
|
+static int thread_result_add(struct dmatest_info *info,
|
|
|
+ struct dmatest_result *r, enum dmatest_error_type type,
|
|
|
+ unsigned int n, unsigned int src_off, unsigned int dst_off,
|
|
|
+ unsigned int len, unsigned long data)
|
|
|
+{
|
|
|
+ struct dmatest_thread_result *tr;
|
|
|
+
|
|
|
+ tr = kzalloc(sizeof(*tr), GFP_KERNEL);
|
|
|
+ if (!tr)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ tr->type = type;
|
|
|
+ tr->n = n;
|
|
|
+ tr->src_off = src_off;
|
|
|
+ tr->dst_off = dst_off;
|
|
|
+ tr->len = len;
|
|
|
+ tr->data = data;
|
|
|
+
|
|
|
+ mutex_lock(&info->results_lock);
|
|
|
+ list_add_tail(&tr->node, &r->results);
|
|
|
+ mutex_unlock(&info->results_lock);
|
|
|
+
|
|
|
+ pr_warn("%s\n", thread_result_get(r->name, tr));
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void result_free(struct dmatest_info *info, const char *name)
|
|
|
+{
|
|
|
+ struct dmatest_result *r, *_r;
|
|
|
+
|
|
|
+ mutex_lock(&info->results_lock);
|
|
|
+ list_for_each_entry_safe(r, _r, &info->results, node) {
|
|
|
+ struct dmatest_thread_result *tr, *_tr;
|
|
|
+
|
|
|
+ if (name && strcmp(r->name, name))
|
|
|
+ continue;
|
|
|
+
|
|
|
+ list_for_each_entry_safe(tr, _tr, &r->results, node) {
|
|
|
+ list_del(&tr->node);
|
|
|
+ kfree(tr);
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(r->name);
|
|
|
+ list_del(&r->node);
|
|
|
+ kfree(r);
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&info->results_lock);
|
|
|
+}
|
|
|
+
|
|
|
+static struct dmatest_result *result_init(struct dmatest_info *info,
|
|
|
+ const char *name)
|
|
|
+{
|
|
|
+ struct dmatest_result *r;
|
|
|
+
|
|
|
+ r = kzalloc(sizeof(*r), GFP_KERNEL);
|
|
|
+ if (r) {
|
|
|
+ r->name = kstrdup(name, GFP_KERNEL);
|
|
|
+ INIT_LIST_HEAD(&r->results);
|
|
|
+ mutex_lock(&info->results_lock);
|
|
|
+ list_add_tail(&r->node, &info->results);
|
|
|
+ mutex_unlock(&info->results_lock);
|
|
|
+ }
|
|
|
+ return r;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* This function repeatedly tests DMA transfers of various lengths and
|
|
|
* offsets for a given operation type until it is told to exit by
|
|
@@ -339,6 +468,7 @@ static int dmatest_func(void *data)
|
|
|
int src_cnt;
|
|
|
int dst_cnt;
|
|
|
int i;
|
|
|
+ struct dmatest_result *result;
|
|
|
|
|
|
thread_name = current->comm;
|
|
|
set_freezable();
|
|
@@ -370,6 +500,10 @@ static int dmatest_func(void *data)
|
|
|
} else
|
|
|
goto err_thread_type;
|
|
|
|
|
|
+ result = result_init(info, thread_name);
|
|
|
+ if (!result)
|
|
|
+ goto err_srcs;
|
|
|
+
|
|
|
thread->srcs = kcalloc(src_cnt+1, sizeof(u8 *), GFP_KERNEL);
|
|
|
if (!thread->srcs)
|
|
|
goto err_srcs;
|
|
@@ -443,10 +577,10 @@ static int dmatest_func(void *data)
|
|
|
ret = dma_mapping_error(dev->dev, dma_srcs[i]);
|
|
|
if (ret) {
|
|
|
unmap_src(dev->dev, dma_srcs, len, i);
|
|
|
- pr_warn("%s: #%u: mapping error %d with "
|
|
|
- "src_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1, ret,
|
|
|
- src_off, len);
|
|
|
+ thread_result_add(info, result,
|
|
|
+ DMATEST_ET_MAP_SRC,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, ret);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
|
}
|
|
@@ -461,10 +595,10 @@ static int dmatest_func(void *data)
|
|
|
unmap_src(dev->dev, dma_srcs, len, src_cnt);
|
|
|
unmap_dst(dev->dev, dma_dsts, params->buf_size,
|
|
|
i);
|
|
|
- pr_warn("%s: #%u: mapping error %d with "
|
|
|
- "dst_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1, ret,
|
|
|
- dst_off, params->buf_size);
|
|
|
+ thread_result_add(info, result,
|
|
|
+ DMATEST_ET_MAP_DST,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, ret);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
|
}
|
|
@@ -494,10 +628,9 @@ static int dmatest_func(void *data)
|
|
|
unmap_src(dev->dev, dma_srcs, len, src_cnt);
|
|
|
unmap_dst(dev->dev, dma_dsts, params->buf_size,
|
|
|
dst_cnt);
|
|
|
- pr_warning("%s: #%u: prep error with src_off=0x%x "
|
|
|
- "dst_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1,
|
|
|
- src_off, dst_off, len);
|
|
|
+ thread_result_add(info, result, DMATEST_ET_PREP,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, 0);
|
|
|
msleep(100);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
@@ -509,10 +642,9 @@ static int dmatest_func(void *data)
|
|
|
cookie = tx->tx_submit(tx);
|
|
|
|
|
|
if (dma_submit_error(cookie)) {
|
|
|
- pr_warning("%s: #%u: submit error %d with src_off=0x%x "
|
|
|
- "dst_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1, cookie,
|
|
|
- src_off, dst_off, len);
|
|
|
+ thread_result_add(info, result, DMATEST_ET_SUBMIT,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, cookie);
|
|
|
msleep(100);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
@@ -534,15 +666,17 @@ static int dmatest_func(void *data)
|
|
|
* free it this time?" dancing. For now, just
|
|
|
* leave it dangling.
|
|
|
*/
|
|
|
- pr_warning("%s: #%u: test timed out\n",
|
|
|
- thread_name, total_tests - 1);
|
|
|
+ thread_result_add(info, result, DMATEST_ET_TIMEOUT,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, 0);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
|
} else if (status != DMA_SUCCESS) {
|
|
|
- pr_warning("%s: #%u: got completion callback,"
|
|
|
- " but status is \'%s\'\n",
|
|
|
- thread_name, total_tests - 1,
|
|
|
- status == DMA_ERROR ? "error" : "in progress");
|
|
|
+ enum dmatest_error_type type = (status == DMA_ERROR) ?
|
|
|
+ DMATEST_ET_DMA_ERROR : DMATEST_ET_DMA_IN_PROGRESS;
|
|
|
+ thread_result_add(info, result, type,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, status);
|
|
|
failed_tests++;
|
|
|
continue;
|
|
|
}
|
|
@@ -574,16 +708,14 @@ static int dmatest_func(void *data)
|
|
|
PATTERN_DST, false);
|
|
|
|
|
|
if (error_count) {
|
|
|
- pr_warning("%s: #%u: %u errors with "
|
|
|
- "src_off=0x%x dst_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1, error_count,
|
|
|
- src_off, dst_off, len);
|
|
|
+ thread_result_add(info, result, DMATEST_ET_VERIFY,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, error_count);
|
|
|
failed_tests++;
|
|
|
} else {
|
|
|
- pr_debug("%s: #%u: No errors with "
|
|
|
- "src_off=0x%x dst_off=0x%x len=0x%x\n",
|
|
|
- thread_name, total_tests - 1,
|
|
|
- src_off, dst_off, len);
|
|
|
+ thread_result_add(info, result, DMATEST_ET_OK,
|
|
|
+ total_tests, src_off, dst_off,
|
|
|
+ len, 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -807,6 +939,9 @@ static int __restart_threaded_test(struct dmatest_info *info, bool run)
|
|
|
if (run == false)
|
|
|
return 0;
|
|
|
|
|
|
+ /* Clear results from previous run */
|
|
|
+ result_free(info, NULL);
|
|
|
+
|
|
|
/* Copy test parameters */
|
|
|
memcpy(params, &info->dbgfs_params, sizeof(*params));
|
|
|
|
|
@@ -945,6 +1080,35 @@ static const struct file_operations dtf_run_fops = {
|
|
|
.llseek = default_llseek,
|
|
|
};
|
|
|
|
|
|
+static int dtf_results_show(struct seq_file *sf, void *data)
|
|
|
+{
|
|
|
+ struct dmatest_info *info = sf->private;
|
|
|
+ struct dmatest_result *result;
|
|
|
+ struct dmatest_thread_result *tr;
|
|
|
+
|
|
|
+ mutex_lock(&info->results_lock);
|
|
|
+ list_for_each_entry(result, &info->results, node) {
|
|
|
+ list_for_each_entry(tr, &result->results, node)
|
|
|
+ seq_printf(sf, "%s\n",
|
|
|
+ thread_result_get(result->name, tr));
|
|
|
+ }
|
|
|
+
|
|
|
+ mutex_unlock(&info->results_lock);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int dtf_results_open(struct inode *inode, struct file *file)
|
|
|
+{
|
|
|
+ return single_open(file, dtf_results_show, inode->i_private);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct file_operations dtf_results_fops = {
|
|
|
+ .open = dtf_results_open,
|
|
|
+ .read = seq_read,
|
|
|
+ .llseek = seq_lseek,
|
|
|
+ .release = single_release,
|
|
|
+};
|
|
|
+
|
|
|
static int dmatest_register_dbgfs(struct dmatest_info *info)
|
|
|
{
|
|
|
struct dentry *d;
|
|
@@ -1015,6 +1179,12 @@ static int dmatest_register_dbgfs(struct dmatest_info *info)
|
|
|
if (IS_ERR_OR_NULL(d))
|
|
|
goto err_node;
|
|
|
|
|
|
+ /* Results of test in progress */
|
|
|
+ d = debugfs_create_file("results", S_IRUGO, info->root, info,
|
|
|
+ &dtf_results_fops);
|
|
|
+ if (IS_ERR_OR_NULL(d))
|
|
|
+ goto err_node;
|
|
|
+
|
|
|
return 0;
|
|
|
|
|
|
err_node:
|
|
@@ -1035,6 +1205,9 @@ static int __init dmatest_init(void)
|
|
|
mutex_init(&info->lock);
|
|
|
INIT_LIST_HEAD(&info->channels);
|
|
|
|
|
|
+ mutex_init(&info->results_lock);
|
|
|
+ INIT_LIST_HEAD(&info->results);
|
|
|
+
|
|
|
/* Set default parameters */
|
|
|
params->buf_size = test_buf_size;
|
|
|
strlcpy(params->channel, test_channel, sizeof(params->channel));
|
|
@@ -1065,6 +1238,7 @@ static void __exit dmatest_exit(void)
|
|
|
|
|
|
debugfs_remove_recursive(info->root);
|
|
|
stop_threaded_test(info);
|
|
|
+ result_free(info, NULL);
|
|
|
}
|
|
|
module_exit(dmatest_exit);
|
|
|
|