在driver/dma/dmatest.c 可以测试dma client.
config DMATEST
tristate "DMA Test client"
depends on DMA_ENGINE
select DMA_ENGINE_RAID
help
Simple DMA test client. Say N unless you're debugging a
DMA Device driver.
#dmatest
obj-$(CONFIG_DMATEST) += dmatest.o
一般情况下会把dmatest.c build 成ko。这样的话,就可以通过下面三种方法给这个ko传递参数
Example of usage:
% modprobe dmatest channel=dma0chan0 timeout=2000 iterations=1 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 1 > /sys/module/dmatest/parameters/run
...or on the kernel command line:
dmatest.channel=dma0chan0 dmatest.timeout=2000 dmatest.iterations=1 dmatest.run=1
static int __init dmatest_init(void)
{
struct dmatest_info *info = &test_info;
struct dmatest_params *params = &info->params;
//由于dmatest.run=1,所以这里的dmatest_run 为1,所以调用run_threaded_test
if (dmatest_run) {
mutex_lock(&info->lock);
run_threaded_test(info);
mutex_unlock(&info->lock);
}
if (params->iterations && wait)
wait_event(thread_wait, !is_threaded_test_run(info));
/* module parameters are stable, inittime tests are started,
* let userspace take over 'run' control
*/
info->did_init = true;
return 0;
}
/* when compiled-in wait for drivers to load first */
late_initcall(dmatest_init);
static void run_threaded_test(struct dmatest_info *info)
{
struct dmatest_params *params = &info->params;
这段主要是设置参数,有点参数是外部传递过来的。例如timeout
/* Copy test parameters */
params->buf_size = test_buf_size;
strlcpy(params->channel, strim(test_channel), sizeof(params->channel));
strlcpy(params->device, strim(test_device), sizeof(params->device));
params->threads_per_chan = threads_per_chan;
params->max_channels = max_channels;
params->iterations = iterations;
params->xor_sources = xor_sources;
params->pq_sources = pq_sources;
params->timeout = timeout;
params->noverify = noverify;
// 可以看到主要测试client的memcpy/xor/sg/pq
request_channels(info, DMA_MEMCPY);
request_channels(info, DMA_XOR);
request_channels(info, DMA_SG);
request_channels(info, DMA_PQ);
}
这里以memcpy 为例
static void request_channels(struct dmatest_info *info,
enum dma_transaction_type type)
{
dma_cap_mask_t mask;
//将dma_cap_mask_t mask 先清0再全部置1
dma_cap_zero(mask);
dma_cap_set(type, mask);
for (;;) {
struct dmatest_params *params = &info->params;
struct dma_chan *chan;
//使用dma client的时候需要先申请一个dma channel
chan = dma_request_channel(mask, filter, params);
if (chan) {
//申请dma channel 成功后调用dmatest_add_channel 测试
if (dmatest_add_channel(info, chan)) {
dma_release_channel(chan);
break; /* add_channel failed, punt */
}
} else
break; /* no more channels available */
if (params->max_channels &&
info->nr_channels >= params->max_channels)
break; /* we have all we need */
}
}
static int dmatest_add_channel(struct dmatest_info *info,
struct dma_chan *chan)
{
struct dmatest_chan *dtc;
struct dma_device *dma_dev = chan->device;
unsigned int thread_count = 0;
int cnt;
dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);
if (!dtc) {
pr_warn("No memory for %s\n", dma_chan_name(chan));
return -ENOMEM;
}
dtc->chan = chan;
INIT_LIST_HEAD(&dtc->threads);
//检查dma controller是否有能力做DMA_MEMCPY,如果有的话,调用dmatest_add_threads 测试
if (dma_has_cap(DMA_MEMCPY, dma_dev->cap_mask)) {
if (dmatest == 0) {
cnt = dmatest_add_threads(info, dtc, DMA_MEMCPY);
thread_count += cnt > 0 ? cnt : 0;
}
}
pr_info("Started %u threads using %s\n",
thread_count, dma_chan_name(chan));
list_add_tail(&dtc->node, &info->channels);
info->nr_channels++;
return 0;
}
static int dmatest_add_threads(struct dmatest_info *info,
struct dmatest_chan *dtc, enum dma_transaction_type type)
{
struct dmatest_params *params = &info->params;
struct dmatest_thread *thread;
struct dma_chan *chan = dtc->chan;
char *op;
unsigned int i;
//这里的op是copy
if (type == DMA_MEMCPY)
op = "copy";
else if (type == DMA_SG)
op = "sg";
else if (type == DMA_XOR)
op = "xor";
else if (type == DMA_PQ)
op = "pq";
else
return -EINVAL;
for (i = 0; i < params->threads_per_chan; i++) {
thread = kzalloc(sizeof(struct dmatest_thread), GFP_KERNEL);
if (!thread) {
pr_warn("No memory for %s-%s%u\n",
dma_chan_name(chan), op, i);
break;
}
thread->info = info;
thread->chan = dtc->chan;
thread->type = type;
smp_wmb();
//新建一个thread dmatest_func
thread->task = kthread_create(dmatest_func, thread, "%s-%s%u",
dma_chan_name(chan), op, i);
if (IS_ERR(thread->task)) {
pr_warn("Failed to create thread %s-%s%u\n",
dma_chan_name(chan), op, i);
kfree(thread);
break;
}
/* srcbuf and dstbuf are allocated by the thread itself */
get_task_struct(thread->task);
list_add_tail(&thread->node, &dtc->threads);
//让thread->task 这个thread 开始工作
wake_up_process(thread->task);
}
return i;
}
在dmatest_func 中按照下面四步来测试memcpy
1:通过dmaengine_get_unmap_data申请一个 struct dmaengine_unmap_data *um;
um = dmaengine_get_unmap_data(dev->dev, src_cnt + dst_cnt, GFP_KERNEL);
2:调用dma_map_page 分别映射要memcpy的原地址和目的地址
for (i = 0; i < src_cnt; i++) {
void *buf = thread->srcs[i];
struct page *pg = virt_to_page(buf);
unsigned long pg_off = offset_in_page(buf);
um->addr[i] = dma_map_page(dev->dev, pg, pg_off,
um->len, DMA_TO_DEVICE);
srcs[i] = um->addr[i] + src_off;
ret = dma_mapping_error(dev->dev, um->addr[i]);
if (ret) {
dmaengine_unmap_put(um);
result("src mapping error", total_tests,
src_off, dst_off, len, ret);
failed_tests++;
continue;
}
um->to_cnt++;
}
3:调用device_prep_dma_memcpy 准备开始发送的地址
if (thread->type == DMA_MEMCPY)
tx = dev->device_prep_dma_memcpy(chan,
dsts[0] + dst_off,
srcs[0], len, flags);
4:设置一个一个callback这样当dma 完成memcpy操作的时候会调用这个callback函数
done.done = false;
tx->callback = dmatest_callback;
tx->callback_param = &done;
cookie = tx->tx_submit(tx);
5:调用 dma_async_issue_pending(chan);来是memcpy的操作
6:通过wait queue等待操作完成,
wait_event_freezable_timeout(done_wait, done.done, msecs_to_jiffies(params->timeout));
这个wait queue在dmatest_func 刚开始时初始化
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(done_wait);
struct dmatest_thread *thread = data;
struct dmatest_done done = { .wait = &done_wait };
最终在dmatest_callback中 唤醒wait queue
static void dmatest_callback(void *arg)
{
struct dmatest_done *done = arg;
done->done = true;
wake_up_all(done->wait);
}
7:最后通过 status = dma_async_is_tx_complete(chan, cookie, NULL, NULL);
来得到memcpy操作的结果,正常情况下应该是DMA_COMPLETE