【问题标题】:Linux DMA: Using the DMAengine for scatter-gather transactionsLinux DMA:使用 DMA 引擎进行分散-收集事务
【发布时间】:2016-09-04 07:10:01
【问题描述】:

我尝试使用自定义内核驱动程序中的 DMAengine API 来执行 scatter-gather 操作。我有一个连续的内存区域作为源,我想通过 scatterlist 结构将其数据复制到多个分布式缓冲区中。 DMA 控制器是支持 DMAengine API 的 PL330 控制器(请参阅PL330 DMA controller)。

我的测试代码如下:

在我的驱动头文件(test_driver.h)中:

#ifndef __TEST_DRIVER_H__
#define __TEST_DRIVER_H__

#include <linux/platform_device.h>
#include <linux/device.h>

#include <linux/scatterlist.h>
#include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/of_dma.h>

#define SG_ENTRIES 3
#define BUF_SIZE 16
#define DEV_BUF 0x10000000

struct dma_block {
    void * data;
    int size;
};

struct dma_private_info {

    struct sg_table sgt;

    struct dma_block * blocks;
    int nblocks;

    int dma_started;

    struct dma_chan * dma_chan;
    struct dma_slave_config dma_config;
    struct dma_async_tx_descriptor * dma_desc;
    dma_cookie_t cookie;
};

struct test_platform_device {
    struct platform_device * pdev;

    struct dma_private_info dma_priv;
};

#define _get_devp(tdev) (&((tdev)->pdev->dev))
#define _get_dmapip(tdev) (&((tdev)->dma_priv))

int dma_stop(struct test_platform_device * tdev);
int dma_start(struct test_platform_device * tdev);
int dma_start_block(struct test_platform_device * tdev);
int dma_init(struct test_platform_device * tdev);
int dma_exit(struct test_platform_device * tdev);

#endif

在我包含 dma 函数的源代码中 (dma_functions.c):

#include <linux/slab.h>

#include "test_driver.h"

#define BARE_RAM_BASE 0x10000000
#define BARE_RAM_SIZE 0x10000000

struct ram_bare {
    uint32_t * __iomem map;

    uint32_t base;
    uint32_t size;
};

static void dma_sg_check(struct test_platform_device * tdev)
{
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);
    uint32_t * buf;
    unsigned int bufsize;
    int nwords;
    int nbytes_word = sizeof(uint32_t);
    int nblocks;
    struct ram_bare ramb;
    uint32_t * p;
    int i;
    int j;

    ramb.map = ioremap(BARE_RAM_BASE,BARE_RAM_SIZE);
    ramb.base = BARE_RAM_BASE;
    ramb.size = BARE_RAM_SIZE;

    dev_info(dev,"nblocks: %d \n",dma_priv->nblocks);

    p = ramb.map;

    nblocks = dma_priv->nblocks;

    for( i = 0 ; i < nblocks ; i++ ) {

        buf = (uint32_t *) dma_priv->blocks[i].data;
        bufsize = dma_priv->blocks[i].size;
        nwords = dma_priv->blocks[i].size/nbytes_word;

        dev_info(dev,"block[%d],size %d: ",i,bufsize);

        for ( j = 0 ; j <  nwords; j++, p++) {
            dev_info(dev,"DMA: 0x%x, RAM: 0x%x",buf[j],ioread32(p));
        }
    }

    iounmap(ramb.map);
}

static int dma_sg_exit(struct test_platform_device * tdev)
{
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    int ret = 0;
    int i;

    for( i = 0 ; i < dma_priv->nblocks ; i++ ) {
        kfree(dma_priv->blocks[i].data);
    }

    kfree(dma_priv->blocks);

    sg_free_table(&(dma_priv->sgt));

    return ret;
}

int dma_stop(struct test_platform_device * tdev)
{
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);
    int ret = 0;

    dma_unmap_sg(dev,dma_priv->sgt.sgl,\
        dma_priv->sgt.nents, DMA_FROM_DEVICE);

    dma_sg_exit(tdev);

    dma_priv->dma_started = 0;

    return ret;
}

static void dma_callback(void * param)
{
    enum dma_status dma_stat;
    struct test_platform_device * tdev = (struct test_platform_device *) param;
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);

    dev_info(dev,"Checking the DMA state....\n");

    dma_stat = dma_async_is_tx_complete(dma_priv->dma_chan,\
        dma_priv->cookie, NULL, NULL);

    if(dma_stat == DMA_COMPLETE) {
        dev_info(dev,"DMA complete! \n");
        dma_sg_check(tdev);
        dma_stop(tdev);
    } else if (unlikely(dma_stat == DMA_ERROR)) {
        dev_info(dev,"DMA error! \n");
        dma_stop(tdev);
    }
}

static void dma_busy_loop(struct test_platform_device * tdev)
{
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);

    enum dma_status status;
    int status_change = -1;

    do {
        status = dma_async_is_tx_complete(dma_priv->dma_chan, dma_priv->cookie, NULL, NULL);

        switch(status) {
        case DMA_COMPLETE:
            if(status_change != 0)
                dev_info(dev,"DMA status: COMPLETE\n");
            status_change = 0;
            break;
        case DMA_PAUSED:
            if (status_change != 1)
                dev_info(dev,"DMA status: PAUSED\n");
            status_change = 1;
            break;
        case DMA_IN_PROGRESS:
            if(status_change != 2)
                dev_info(dev,"DMA status: IN PROGRESS\n");
            status_change = 2;
            break;
        case DMA_ERROR:
            if (status_change != 3)
                dev_info(dev,"DMA status: ERROR\n");
            status_change = 3;
            break;
        default:
            dev_info(dev,"DMA status: UNKNOWN\n");
            status_change = -1;
            break;
        }
    } while(status != DMA_COMPLETE);

    dev_info(dev,"DMA transaction completed! \n");
}

static int dma_sg_init(struct test_platform_device * tdev)
{

    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct scatterlist *sg;
    int ret = 0;
    int i;

    ret = sg_alloc_table(&(dma_priv->sgt), SG_ENTRIES, GFP_ATOMIC);
    if(ret)
        goto out_mem2;

    dma_priv->nblocks = SG_ENTRIES;
    dma_priv->blocks = (struct dma_block *) kmalloc(dma_priv->nblocks\
        *sizeof(struct dma_block), GFP_ATOMIC);
    if(dma_priv->blocks == NULL) 
         goto out_mem1;


    for( i = 0 ; i < dma_priv->nblocks ; i++ ) {
        dma_priv->blocks[i].size = BUF_SIZE;
        dma_priv->blocks[i].data = kmalloc(dma_priv->blocks[i].size, GFP_ATOMIC);
        if(dma_priv->blocks[i].data == NULL)
            goto out_mem3;
    }

    for_each_sg(dma_priv->sgt.sgl, sg, dma_priv->sgt.nents, i)
        sg_set_buf(sg,dma_priv->blocks[i].data,dma_priv->blocks[i].size);

    return ret;

out_mem3:
    i--;

    while(i >= 0)
        kfree(dma_priv->blocks[i].data);

    kfree(dma_priv->blocks);

out_mem2:
    sg_free_table(&(dma_priv->sgt));

out_mem1:
    ret = -ENOMEM;  

    return ret;

}

static int _dma_start(struct test_platform_device * tdev,int block)
{
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);
    int ret = 0;
    int sglen;

    /* Step 1: Allocate and initialize the SG list */
    dma_sg_init(tdev);

    /* Step 2: Map the SG list */
    sglen = dma_map_sg(dev,dma_priv->sgt.sgl,\
        dma_priv->sgt.nents, DMA_FROM_DEVICE);
    if(! sglen)
        goto out2;

    /* Step 3: Configure the DMA */
    (dma_priv->dma_config).direction = DMA_DEV_TO_MEM;
    (dma_priv->dma_config).src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    (dma_priv->dma_config).src_maxburst = 1;
    (dma_priv->dma_config).src_addr = (dma_addr_t) DEV_BUF;

    dmaengine_slave_config(dma_priv->dma_chan, \
        &(dma_priv->dma_config));

    /* Step 4: Prepare the SG descriptor */
    dma_priv->dma_desc = dmaengine_prep_slave_sg(dma_priv->dma_chan, \
        dma_priv->sgt.sgl, dma_priv->sgt.nents, DMA_DEV_TO_MEM, \
        DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
    if (dma_priv->dma_desc == NULL) {
        dev_err(dev,"DMA could not assign a descriptor! \n");
        goto out1;
    }

    /* Step 5: Set the callback method */
    (dma_priv->dma_desc)->callback = dma_callback;
    (dma_priv->dma_desc)->callback_param = (void *) tdev;

    /* Step 6: Put the DMA descriptor in the queue */
    dma_priv->cookie = dmaengine_submit(dma_priv->dma_desc);

    /* Step 7: Fires the DMA transaction */
    dma_async_issue_pending(dma_priv->dma_chan);

    dma_priv->dma_started = 1;

    if(block)
        dma_busy_loop(tdev);

    return ret;

out1:
    dma_stop(tdev);
out2:
    ret = -1;

    return ret;
}

int dma_start(struct test_platform_device * tdev) {
    return _dma_start(tdev,0);
}

int dma_start_block(struct test_platform_device * tdev) {
    return _dma_start(tdev,1);
}

int dma_init(struct test_platform_device * tdev)
{
    int ret = 0;
    struct dma_private_info * dma_priv = _get_dmapip(tdev);
    struct device * dev = _get_devp(tdev);

    dma_priv->dma_chan = dma_request_slave_channel(dev, \
        "dma_chan0");
    if (dma_priv->dma_chan == NULL) {
        dev_err(dev,"DMA channel busy! \n");
        ret = -1;
    }

    dma_priv->dma_started = 0;

    return ret;
}

int dma_exit(struct test_platform_device * tdev)
{
    int ret = 0;
    struct dma_private_info * dma_priv = _get_dmapip(tdev);

    if(dma_priv->dma_started) {
        dmaengine_terminate_all(dma_priv->dma_chan);
        dma_stop(tdev);
        dma_priv->dma_started = 0;
    }

    if(dma_priv->dma_chan != NULL)
        dma_release_channel(dma_priv->dma_chan);

    return ret;
}

在我的驱动源文件(test_driver.c)中:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>

#include "test_driver.h"

static int dma_block=0;
module_param_named(dma_block, dma_block, int, 0444);

static struct test_platform_device tdev;

static struct of_device_id test_of_match[] = {
  { .compatible = "custom,test-driver-1.0", },
  {}
};

static int test_probe(struct platform_device *op)
{
    int ret = 0;
    struct device * dev = &(op->dev);

    const struct of_device_id *match = of_match_device(test_of_match, &op->dev);

    if (!match)
        return -EINVAL;

    tdev.pdev = op;

    dma_init(&tdev);

    if(dma_block)
        ret = dma_start_block(&tdev);
    else
        ret = dma_start(&tdev);

    if(ret) {
        dev_err(dev,"Error to start DMA transaction! \n");
    } else {
        dev_info(dev,"DMA OK! \n");
    }

    return ret;
}

static int test_remove(struct platform_device *op)
{       
    dma_exit(&tdev);

    return 0;
}

static struct platform_driver test_platform_driver = {
  .probe = test_probe,
  .remove = test_remove,
  .driver = {
    .name = "test-driver",
    .owner = THIS_MODULE,
    .of_match_table = test_of_match,
  },
};

static int test_init(void)
{
    platform_driver_register(&test_platform_driver);
    return 0;
}

static void test_exit(void)
{
    platform_driver_unregister(&test_platform_driver);
}

module_init(test_init);
module_exit(test_exit);

MODULE_AUTHOR("klyone");
MODULE_DESCRIPTION("DMA SG test module");
MODULE_LICENSE("GPL");

但是,DMA 从不调用我的回调函数,我也不知道为什么会这样。也许,我误会了什么……

谁能帮帮我?

提前致谢。

【问题讨论】:

  • DMA不是C标准的一部分,也不是POSIX等通用库,你在说什么?
  • 而C不支持方法
  • DMAengine 是 Linux 内核中使用 DMA 驱动程序的 API。很抱歉造成混乱,我想我可以改进我的问题描述......
  • 显示您的实际代码。鉴于dma_priv.dma_desc 是一个指针,dma_priv.dma_desc.callback = dma_callback; 行显然不会编译。您还应该显示调用dma_initdma_start 的代码。
  • 如果您剪切并粘贴您的实际代码,则不能出现任何拼写错误。任何人试图调试你实际上没有使用的代码是没有意义的。

标签: c linux linux-kernel linux-device-driver dma


【解决方案1】:

警告:我没有给你一个明确的解决方案,而只是一些关于如何调试的观察和建议[基于多年编写/调试 linux 设备驱动程序的经验]。

我认为您认为回调没有正在完成,因为您没有收到任何 printk 消息。但是,回调是 only 拥有它们的地方。但是,printk 级别设置是否足够高以查看消息?我会在你的模块初始化中添加一个dev_info,以证明它按预期打印。

另外,如果dma_start 没有按预期工作,你[可能] 不会收到回调,所以我也会在那里添加一些dev_info 调用(例如,在步骤 7 中调用之前和之后) .我还注意到dma_start 中并非所有调用检查错误都会返回[可能没问题或无效返回,只是提一下以防你错过了一个]

此时,需要注意的是,这里真的有两个问题:(1)你的DMA请求是否启动成功[并且完成]? (2) 收到回电了吗?

所以,我会将dma_complete 中的一些代码拆分为(例如)dma_test_done。后者进行相同的检查,但只打印“完成”消息。您可以在轮询模式下调用它来验证 DMA 是否完成。

所以,如果您 [最终] 得到了一个完成,那么问题就归结为您没有得到回调的原因。但是,如果你 [甚至] 没有完成,那就是一个更根本的问题。

这让我想起了。您没有显示任何调用dma_start 的代码或您如何等待 完成。我认为如果你的回调工作正常,它会发出某种唤醒,基本级别会等待。或者,回调将执行请求解除分配/清理(即您要编写的更多代码)

在第 7 步,您正在调用 dma_async_issue_pending,它应该调用 pl330_issue_pendingpl330_issue_pending 将调用 pl330_tasklet

pl330_tasklet是一个tasklet函数,但也可以直接调用【在没有活动请求时启动DMA】。

pl330_tasklet 将在其“工作”队列中循环,并将任何已完成的项目移至其“已完成”队列。然后它会尝试开始新的请求。然后它在其完成的队列上循环并发出回调。

pl330_tasklet 获取回调指针,但如果它为 null,则它会被忽略。您已经设置了回调,但最好验证 设置回调的位置是否与pl330_tasklet 从中获取它的位置相同[或传播到]。

当您拨打电话时,可能一切都忙,所以没有没有个已完成的请求,没有个空间来开始一个新的请求,所以什么都没有 em> 完成。在这种情况下,稍后将再次调用pl330_tasklet

所以,当dma_async_issue_pending 返回时,什么都可能还没有发生。您的情况很有可能发生这种情况。

pl330_tasklet 尝试通过调用 fill_queue 来启动新的 DMA。它将通过查看status != BUSY 来检查描述符是否[已经] 忙碌。因此,您可能希望验证您的值是否正确。否则,您将永远收到回调 [甚至任何 DMA 启动]。

然后,fill_queue 将尝试通过pl330_submit_req 发起请求。但是,这可能会返回错误(例如,队列已满),因此,事情再次被推迟。

作为参考,请注意pl330_submit_req顶部的以下评论:

Submit a list of xfers after which the client wants notification.
Client is not notified after each xfer unit, just once after all
xfer units are done or some error occurs.

我要做的是开始破解pl330.c 并添加调试消息和交叉检查。如果您的系统使得 pl330 为 许多 个其他请求提供服务,您可以通过检查设备的私有数据指针是否与您的匹配来限制调试消息。

特别是,您希望在请求实际开始时收到一条消息,因此您可以在pl330_submit_req 末尾添加一条调试消息

然后,在 pl330_tasklet 中添加请求消息也会有所帮助。

这是两个很好的起点。但是,不要害怕根据需要添加更多 printk 调用。您可能会对调用[或调用]或调用顺序感到惊讶。


更新:

如果我安装了具有阻塞行为的内核模块,那么一切都会很好地初始化。但是,dma_busy_loop 函数显示 DMA 描述符始终处于 IN PROGESS 并且 DMA 事务永远不会完成。因此,不会执行回调函数。会发生什么?

做了更多的研究。 Cookie 只是递增的序列号。例如,如果您发出一个请求,该请求被分解为 [比如说] 10 个单独的分散/收集操作 [描述符],每个操作都会获得一个唯一的 cookie 值。 cookie 返回值是最新/最后一个(例如 10)。

当你调用 (1) dma_async_is_tx_complete, (2) 它调用 chan-&gt;device-&gt;device_tx_status, (3) 这是 pl330_tx_status, (4) 它调用 dma_cookie_status

旁注/提示:当我追踪这个问题时,我一直在dmaengine.hpl330.c 之间来回切换。就像:看(1),它调用(2)。那个套装在哪里?我猜是pl330.c。所以,我对字符串进行了搜索,并得到了 pl330 函数的名称(即(3))。所以,我去那里,看到它确实如此(4)。所以...返回dmaengine.h...

但是,当您进行外部调用时,您将忽略 [设置为 NULL] 最后两个参数。这些可能很有用,因为它们返回“最后一个”和“使用过的”cookie。因此,即使您没有完全完成,这些值也可能会发生变化并显示部分进度。

其中一个最终应该 >= “返回” cookie 值。 (即)整个操作应该是完整的。因此,这将有助于区分可能发生的情况。

另外,请注意在dmaengine.h 的正下方dma_async_is_tx_complete 中有dma_async_is_complete。该函数根据您传递的 cookie 值以及“last”和“used”cookie 值决定是返回 DMA_COMPLETE 还是 DMA_IN_PROGRESS。它是被动的,未在代码路径 [AFAICT] 中使用,但它确实显示了如何自己计算完成度。

【讨论】:

  • 首先,感谢您与我分享您的经验。我改进了我的代码以找出问题所在。现在,我的驱动程序接受一个允许配置阻塞或非阻塞行为(等待或不等待 DMA 事务)的 dma_block 参数。如果我安装具有阻塞行为的内核模块,则一切都初始化得很好。但是,dma_busy_loop 函数显示 DMA 描述符始终为IN PROGESS,并且 DMA 事务永远不会完成。因此,不会执行回调函数。会发生什么?谢谢。
  • 我很抱歉花了这么长时间才回复您。我在我的答案中添加了一些更具体的信息。不知道你处于什么状态,或者你已经取得了什么样的进步,所以,希望你已经启动并运行。不管怎样,我很想知道。
  • 抱歉,回复您的时间太长了。我的问题比我们上面讨论的所有问题都更基本。我尝试在两个 RAM 内存区域之间执行 sg 操作,并且我检查过它不喜欢 PL330 驱动程序。它假定设备地址总是使用 FIFO。由于这个原因,DMA 没有显示任何进度并且回调函数没有被执行。结论是我必须向驱动程序添加一个新功能,以便能够在这些情况下运行 sg 操作。非常感谢您的支持!
猜你喜欢
  • 2016-03-31
  • 2014-10-15
  • 2017-07-14
  • 2013-08-05
  • 1970-01-01
  • 2014-06-24
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
相关资源
最近更新 更多