diff options
-rw-r--r-- | Documentation/driver-api/dmaengine/dmatest.rst | 109 | ||||
-rw-r--r-- | drivers/dma/dmatest.c | 299 |
2 files changed, 349 insertions, 59 deletions
diff --git a/Documentation/driver-api/dmaengine/dmatest.rst b/Documentation/driver-api/dmaengine/dmatest.rst index 7ce5e71c353e..dd5263a19c7b 100644 --- a/Documentation/driver-api/dmaengine/dmatest.rst +++ b/Documentation/driver-api/dmaengine/dmatest.rst @@ -26,28 +26,43 @@ Part 2 - When dmatest is built as a module Example of usage:: - % modprobe dmatest channel=dma0chan0 timeout=2000 iterations=1 run=1 + % modprobe dmatest timeout=2000 iterations=1 channel=dma0chan0 run=1 ...or:: % modprobe dmatest - % echo dma0chan0 > /sys/module/dmatest/parameters/channel % echo 2000 > /sys/module/dmatest/parameters/timeout % echo 1 > /sys/module/dmatest/parameters/iterations + % echo dma0chan0 > /sys/module/dmatest/parameters/channel % echo 1 > /sys/module/dmatest/parameters/run ...or on the kernel command line:: - dmatest.channel=dma0chan0 dmatest.timeout=2000 dmatest.iterations=1 dmatest.run=1 + dmatest.timeout=2000 dmatest.iterations=1 dmatest.channel=dma0chan0 dmatest.run=1 + +Example of multi-channel test usage: + % modprobe dmatest + % echo 2000 > /sys/module/dmatest/parameters/timeout + % echo 1 > /sys/module/dmatest/parameters/iterations + % echo dma0chan0 > /sys/module/dmatest/parameters/channel + % echo dma0chan1 > /sys/module/dmatest/parameters/channel + % echo dma0chan2 > /sys/module/dmatest/parameters/channel + % echo 1 > /sys/module/dmatest/parameters/run +Note: the channel parameter should always be the last parameter set prior to +running the test (setting run=1), this is because upon setting the channel +parameter, that specific channel is requested using the dmaengine and a thread +is created with the existing parameters. This thread is set as pending +and will be executed once run is set to 1. Any parameters set after the thread +is created are not applied. .. hint:: available channel list could be extracted by running the following command:: % ls -1 /sys/class/dma/ -Once started a message like "dmatest: Started 1 threads using dma0chan0" is -emitted. After that only test failure messages are reported until the test -stops. +Once started a message like " dmatest: Added 1 threads using dma0chan0" is +emitted. A thread for that specific channel is created and is now pending, the +pending thread is started once run is to 1. Note that running a new test will not stop any in progress test. @@ -112,3 +127,85 @@ Example:: The details of a data miscompare error are also emitted, but do not follow the above format. + +Part 5 - Handling channel allocation +==================================== + +Allocating Channels +------------------- + +Channels are required to be configured prior to starting the test run. +Attempting to run the test without configuring the channels will fail. + +Example:: + + % echo 1 > /sys/module/dmatest/parameters/run + dmatest: Could not start test, no channels configured + +Channels are registered using the "channel" parameter. Channels can be requested by their +name, once requested, the channel is registered and a pending thread is added to the test list. + +Example:: + + % echo dma0chan2 > /sys/module/dmatest/parameters/channel + dmatest: Added 1 threads using dma0chan2 + +More channels can be added by repeating the example above. +Reading back the channel parameter will return the name of last channel that was added successfully. + +Example:: + + % echo dma0chan1 > /sys/module/dmatest/parameters/channel + dmatest: Added 1 threads using dma0chan1 + % echo dma0chan2 > /sys/module/dmatest/parameters/channel + dmatest: Added 1 threads using dma0chan2 + % cat /sys/module/dmatest/parameters/channel + dma0chan2 + +Another method of requesting channels is to request a channel with an empty string, Doing so +will request all channels available to be tested: + +Example:: + + % echo "" > /sys/module/dmatest/parameters/channel + dmatest: Added 1 threads using dma0chan0 + dmatest: Added 1 threads using dma0chan3 + dmatest: Added 1 threads using dma0chan4 + dmatest: Added 1 threads using dma0chan5 + dmatest: Added 1 threads using dma0chan6 + dmatest: Added 1 threads using dma0chan7 + dmatest: Added 1 threads using dma0chan8 + +At any point during the test configuration, reading the "test_list" parameter will +print the list of currently pending tests. + +Example:: + + % cat /sys/module/dmatest/parameters/test_list + dmatest: 1 threads using dma0chan0 + dmatest: 1 threads using dma0chan3 + dmatest: 1 threads using dma0chan4 + dmatest: 1 threads using dma0chan5 + dmatest: 1 threads using dma0chan6 + dmatest: 1 threads using dma0chan7 + dmatest: 1 threads using dma0chan8 + +Note: Channels will have to be configured for each test run as channel configurations do not +carry across to the next test run. + +Releasing Channels +------------------- + +Channels can be freed by setting run to 0. + +Example:: + % echo dma0chan1 > /sys/module/dmatest/parameters/channel + dmatest: Added 1 threads using dma0chan1 + % cat /sys/class/dma/dma0chan1/in_use + 1 + % echo 0 > /sys/module/dmatest/parameters/run + % cat /sys/class/dma/dma0chan1/in_use + 0 + +Channels allocated by previous test runs are automatically freed when a new +channel is requested after completing a successful test run. diff --git a/drivers/dma/dmatest.c b/drivers/dma/dmatest.c index aa1712beb0cc..2eea4ef72915 100644 --- a/drivers/dma/dmatest.c +++ b/drivers/dma/dmatest.c @@ -27,11 +27,6 @@ static unsigned int test_buf_size = 16384; module_param(test_buf_size, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(test_buf_size, "Size of the memcpy test buffer"); -static char test_channel[20]; -module_param_string(channel, test_channel, sizeof(test_channel), - S_IRUGO | S_IWUSR); -MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)"); - static char test_device[32]; module_param_string(device, test_device, sizeof(test_device), S_IRUGO | S_IWUSR); @@ -84,6 +79,14 @@ static bool verbose; module_param(verbose, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(verbose, "Enable \"success\" result messages (default: off)"); +static int alignment = -1; +module_param(alignment, int, 0644); +MODULE_PARM_DESC(alignment, "Custom data address alignment taken as 2^(alignment) (default: not used (-1))"); + +static unsigned int transfer_size; +module_param(transfer_size, uint, 0644); +MODULE_PARM_DESC(transfer_size, "Optional custom transfer size in bytes (default: not used (0))"); + /** * struct dmatest_params - test parameters. * @buf_size: size of the memcpy test buffer @@ -108,6 +111,8 @@ struct dmatest_params { int timeout; bool noverify; bool norandom; + int alignment; + unsigned int transfer_size; }; /** @@ -139,6 +144,28 @@ static bool dmatest_run; module_param_cb(run, &run_ops, &dmatest_run, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(run, "Run the test (default: false)"); +static int dmatest_chan_set(const char *val, const struct kernel_param *kp); +static int dmatest_chan_get(char *val, const struct kernel_param *kp); +static const struct kernel_param_ops multi_chan_ops = { + .set = dmatest_chan_set, + .get = dmatest_chan_get, +}; + +static char test_channel[20]; +static struct kparam_string newchan_kps = { + .string = test_channel, + .maxlen = 20, +}; +module_param_cb(channel, &multi_chan_ops, &newchan_kps, 0644); +MODULE_PARM_DESC(channel, "Bus ID of the channel to test (default: any)"); + +static int dmatest_test_list_get(char *val, const struct kernel_param *kp); +static const struct kernel_param_ops test_list_ops = { + .get = dmatest_test_list_get, +}; +module_param_cb(test_list, &test_list_ops, NULL, 0444); +MODULE_PARM_DESC(test_list, "Print current test list"); + /* Maximum amount of mismatched bytes in buffer to print */ #define MAX_ERROR_COUNT 32 @@ -160,6 +187,13 @@ MODULE_PARM_DESC(run, "Run the test (default: false)"); #define PATTERN_COUNT_MASK 0x1f #define PATTERN_MEMSET_IDX 0x01 +/* Fixed point arithmetic ops */ +#define FIXPT_SHIFT 8 +#define FIXPNT_MASK 0xFF +#define FIXPT_TO_INT(a) ((a) >> FIXPT_SHIFT) +#define INT_TO_FIXPT(a) ((a) << FIXPT_SHIFT) +#define FIXPT_GET_FRAC(a) ((((a) & FIXPNT_MASK) * 100) >> FIXPT_SHIFT) + /* poor man's completion - we want to use wait_event_freezable() on it */ struct dmatest_done { bool done; @@ -179,6 +213,7 @@ struct dmatest_thread { wait_queue_head_t done_wait; struct dmatest_done test_done; bool done; + bool pending; }; struct dmatest_chan { @@ -206,6 +241,22 @@ static bool is_threaded_test_run(struct dmatest_info *info) return false; } +static bool is_threaded_test_pending(struct dmatest_info *info) +{ + struct dmatest_chan *dtc; + + list_for_each_entry(dtc, &info->channels, node) { + struct dmatest_thread *thread; + + list_for_each_entry(thread, &dtc->threads, node) { + if (thread->pending) + return true; + } + } + + return false; +} + static int dmatest_wait_get(char *val, const struct kernel_param *kp) { struct dmatest_info *info = &test_info; @@ -419,13 +470,15 @@ static unsigned long long dmatest_persec(s64 runtime, unsigned int val) } per_sec *= val; + per_sec = INT_TO_FIXPT(per_sec); do_div(per_sec, runtime); + return per_sec; } static unsigned long long dmatest_KBs(s64 runtime, unsigned long long len) { - return dmatest_persec(runtime, len >> 10); + return FIXPT_TO_INT(dmatest_persec(runtime, len >> 10)); } /* @@ -466,6 +519,7 @@ static int dmatest_func(void *data) ktime_t comparetime = 0; s64 runtime = 0; unsigned long long total_len = 0; + unsigned long long iops = 0; u8 align = 0; bool is_memset = false; dma_addr_t *srcs; @@ -476,27 +530,32 @@ static int dmatest_func(void *data) ret = -ENOMEM; smp_rmb(); + thread->pending = false; info = thread->info; params = &info->params; chan = thread->chan; dev = chan->device; if (thread->type == DMA_MEMCPY) { - align = dev->copy_align; + align = params->alignment < 0 ? dev->copy_align : + params->alignment; src_cnt = dst_cnt = 1; } else if (thread->type == DMA_MEMSET) { - align = dev->fill_align; + align = params->alignment < 0 ? dev->fill_align : + params->alignment; src_cnt = dst_cnt = 1; is_memset = true; } else if (thread->type == DMA_XOR) { /* force odd to ensure dst = src */ src_cnt = min_odd(params->xor_sources | 1, dev->max_xor); dst_cnt = 1; - align = dev->xor_align; + align = params->alignment < 0 ? dev->xor_align : + params->alignment; } else if (thread->type == DMA_PQ) { /* force odd to ensure dst = src */ src_cnt = min_odd(params->pq_sources | 1, dma_maxpq(dev, 0)); dst_cnt = 2; - align = dev->pq_align; + align = params->alignment < 0 ? dev->pq_align : + params->alignment; pq_coefs = kmalloc(params->pq_sources + 1, GFP_KERNEL); if (!pq_coefs) @@ -507,9 +566,22 @@ static int dmatest_func(void *data) } else goto err_thread_type; + /* Check if buffer count fits into map count variable (u8) */ + if ((src_cnt + dst_cnt) >= 255) { + pr_err("too many buffers (%d of 255 supported)\n", + src_cnt + dst_cnt); + goto err_free_coefs; + } + + if (1 << align > params->buf_size) { + pr_err("%u-byte buffer too small for %d-byte alignment\n", + params->buf_size, 1 << align); + goto err_free_coefs; + } + thread->srcs = kcalloc(src_cnt + 1, sizeof(u8 *), GFP_KERNEL); if (!thread->srcs) - goto err_srcs; + goto err_free_coefs; thread->usrcs = kcalloc(src_cnt + 1, sizeof(u8 *), GFP_KERNEL); if (!thread->usrcs) @@ -576,28 +648,25 @@ static int dmatest_func(void *data) total_tests++; - /* Check if buffer count fits into map count variable (u8) */ - if ((src_cnt + dst_cnt) >= 255) { - pr_err("too many buffers (%d of 255 supported)\n", - src_cnt + dst_cnt); - break; - } - - if (1 << align > params->buf_size) { - pr_err("%u-byte buffer too small for %d-byte alignment\n", - params->buf_size, 1 << align); - break; - } - - if (params->norandom) + if (params->transfer_size) { + if (params->transfer_size >= params->buf_size) { + pr_err("%u-byte transfer size must be lower than %u-buffer size\n", + params->transfer_size, params->buf_size); + break; + } + len = params->transfer_size; + } else if (params->norandom) { len = params->buf_size; - else + } else { len = dmatest_random() % params->buf_size + 1; + } - len = (len >> align) << align; - if (!len) - len = 1 << align; - + /* Do not alter transfer size explicitly defined by user */ + if (!params->transfer_size) { + len = (len >> align) << align; + if (!len) + len = 1 << align; + } total_len += len; if (params->norandom) { @@ -721,14 +790,14 @@ static int dmatest_func(void *data) status = dma_async_is_tx_complete(chan, cookie, NULL, NULL); + dmaengine_unmap_put(um); + if (!done->done) { - dmaengine_unmap_put(um); result("test timed out", total_tests, src_off, dst_off, len, 0); failed_tests++; continue; } else if (status != DMA_COMPLETE) { - dmaengine_unmap_put(um); result(status == DMA_ERROR ? "completion error status" : "completion busy status", total_tests, src_off, @@ -737,8 +806,6 @@ static int dmatest_func(void *data) continue; } - dmaengine_unmap_put(um); - if (params->noverify) { verbose_result("test passed", total_tests, src_off, dst_off, len, 0); @@ -802,17 +869,18 @@ err_srcbuf: kfree(thread->usrcs); err_usrcs: kfree(thread->srcs); -err_srcs: +err_free_coefs: kfree(pq_coefs); err_thread_type: - pr_info("%s: summary %u tests, %u failures %llu iops %llu KB/s (%d)\n", + iops = dmatest_persec(runtime, total_tests); + pr_info("%s: summary %u tests, %u failures %llu.%02llu iops %llu KB/s (%d)\n", current->comm, total_tests, failed_tests, - dmatest_persec(runtime, total_tests), + FIXPT_TO_INT(iops), FIXPT_GET_FRAC(iops), dmatest_KBs(runtime, total_len), ret); /* terminate all transfers on specified channels */ if (ret || failed_tests) - dmaengine_terminate_all(chan); + dmaengine_terminate_sync(chan); thread->done = true; wake_up(&thread_wait); @@ -836,7 +904,7 @@ static void dmatest_cleanup_channel(struct dmatest_chan *dtc) } /* terminate all transfers on specified channels */ - dmaengine_terminate_all(dtc->chan); + dmaengine_terminate_sync(dtc->chan); kfree(dtc); } @@ -886,7 +954,7 @@ static int dmatest_add_threads(struct dmatest_info *info, /* srcbuf and dstbuf are allocated by the thread itself */ get_task_struct(thread->task); list_add_tail(&thread->node, &dtc->threads); - wake_up_process(thread->task); + thread->pending = true; } return i; @@ -932,7 +1000,7 @@ static int dmatest_add_channel(struct dmatest_info *info, thread_count += cnt > 0 ? cnt : 0; } - pr_info("Started %u threads using %s\n", + pr_info("Added %u threads using %s\n", thread_count, dma_chan_name(chan)); list_add_tail(&dtc->node, &info->channels); @@ -977,7 +1045,7 @@ static void request_channels(struct dmatest_info *info, } } -static void run_threaded_test(struct dmatest_info *info) +static void add_threaded_test(struct dmatest_info *info) { struct dmatest_params *params = &info->params; @@ -993,6 +1061,8 @@ static void run_threaded_test(struct dmatest_info *info) params->timeout = timeout; params->noverify = noverify; params->norandom = norandom; + params->alignment = alignment; + params->transfer_size = transfer_size; request_channels(info, DMA_MEMCPY); request_channels(info, DMA_MEMSET); @@ -1000,6 +1070,24 @@ static void run_threaded_test(struct dmatest_info *info) request_channels(info, DMA_PQ); } +static void run_pending_tests(struct dmatest_info *info) +{ + struct dmatest_chan *dtc; + unsigned int thread_count = 0; + + list_for_each_entry(dtc, &info->channels, node) { + struct dmatest_thread *thread; + + thread_count = 0; + list_for_each_entry(thread, &dtc->threads, node) { + wake_up_process(thread->task); + thread_count++; + } + pr_info("Started %u threads using %s\n", + thread_count, dma_chan_name(dtc->chan)); + } +} + static void stop_threaded_test(struct dmatest_info *info) { struct dmatest_chan *dtc, *_dtc; @@ -1016,7 +1104,7 @@ static void stop_threaded_test(struct dmatest_info *info) info->nr_channels = 0; } -static void restart_threaded_test(struct dmatest_info *info, bool run) +static void start_threaded_tests(struct dmatest_info *info) { /* we might be called early to set run=, defer running until all * parameters have been evaluated @@ -1024,11 +1112,7 @@ static void restart_threaded_test(struct dmatest_info *info, bool run) if (!info->did_init) return; - /* Stop any running test first */ - stop_threaded_test(info); - - /* Run test with new parameters */ - run_threaded_test(info); + run_pending_tests(info); } static int dmatest_run_get(char *val, const struct kernel_param *kp) @@ -1039,7 +1123,8 @@ static int dmatest_run_get(char *val, const struct kernel_param *kp) if (is_threaded_test_run(info)) { dmatest_run = true; } else { - stop_threaded_test(info); + if (!is_threaded_test_pending(info)) + stop_threaded_test(info); dmatest_run = false; } mutex_unlock(&info->lock); @@ -1057,18 +1142,125 @@ static int dmatest_run_set(const char *val, const struct kernel_param *kp) if (ret) { mutex_unlock(&info->lock); return ret; + } else if (dmatest_run) { + if (is_threaded_test_pending(info)) + start_threaded_tests(info); + else + pr_info("Could not start test, no channels configured\n"); + } else { + stop_threaded_test(info); } - if (is_threaded_test_run(info)) + mutex_unlock(&info->lock); + + return ret; +} + +static int dmatest_chan_set(const char *val, const struct kernel_param *kp) +{ + struct dmatest_info *info = &test_info; + struct dmatest_chan *dtc; + char chan_reset_val[20]; + int ret = 0; + + mutex_lock(&info->lock); + ret = param_set_copystring(val, kp); + if (ret) { + mutex_unlock(&info->lock); + return ret; + } + /*Clear any previously run threads */ + if (!is_threaded_test_run(info) && !is_threaded_test_pending(info)) + stop_threaded_test(info); + /* Reject channels that are already registered */ + if (is_threaded_test_pending(info)) { + list_for_each_entry(dtc, &info->channels, node) { + if (strcmp(dma_chan_name(dtc->chan), + strim(test_channel)) == 0) { + dtc = list_last_entry(&info->channels, + struct dmatest_chan, + node); + strlcpy(chan_reset_val, + dma_chan_name(dtc->chan), + sizeof(chan_reset_val)); + ret = -EBUSY; + goto add_chan_err; + } + } + } + + add_threaded_test(info); + + /* Check if channel was added successfully */ + dtc = list_last_entry(&info->channels, struct dmatest_chan, node); + + if (dtc->chan) { + /* + * if new channel was not successfully added, revert the + * "test_channel" string to the name of the last successfully + * added channel. exception for when users issues empty string + * to channel parameter. + */ + if ((strcmp(dma_chan_name(dtc->chan), strim(test_channel)) != 0) + && (strcmp("", strim(test_channel)) != 0)) { + ret = -EINVAL; + strlcpy(chan_reset_val, dma_chan_name(dtc->chan), + sizeof(chan_reset_val)); + goto add_chan_err; + } + + } else { + /* Clear test_channel if no channels were added successfully */ + strlcpy(chan_reset_val, "", sizeof(chan_reset_val)); ret = -EBUSY; - else if (dmatest_run) - restart_threaded_test(info, dmatest_run); + goto add_chan_err; + } + + mutex_unlock(&info->lock); + + return ret; +add_chan_err: + param_set_copystring(chan_reset_val, kp); mutex_unlock(&info->lock); return ret; } +static int dmatest_chan_get(char *val, const struct kernel_param *kp) +{ + struct dmatest_info *info = &test_info; + + mutex_lock(&info->lock); + if (!is_threaded_test_run(info) && !is_threaded_test_pending(info)) { + stop_threaded_test(info); + strlcpy(test_channel, "", sizeof(test_channel)); + } + mutex_unlock(&info->lock); + + return param_get_string(val, kp); +} + +static int dmatest_test_list_get(char *val, const struct kernel_param *kp) +{ + struct dmatest_info *info = &test_info; + struct dmatest_chan *dtc; + unsigned int thread_count = 0; + + list_for_each_entry(dtc, &info->channels, node) { + struct dmatest_thread *thread; + + thread_count = 0; + list_for_each_entry(thread, &dtc->threads, node) { + thread_count++; + } + pr_info("%u threads using %s\n", + thread_count, dma_chan_name(dtc->chan)); + } + + return 0; +} + static int __init dmatest_init(void) { struct dmatest_info *info = &test_info; @@ -1076,7 +1268,8 @@ static int __init dmatest_init(void) if (dmatest_run) { mutex_lock(&info->lock); - run_threaded_test(info); + add_threaded_test(info); + run_pending_tests(info); mutex_unlock(&info->lock); } |