Linux DMA:使用DMAengine进行分散 - 收集事务

时间:2016-05-09 15:13:10

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

我尝试使用自定义内核驱动程序中的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从不调用我的回调函数,我不知道它为什么会发生。也许,我误解了一些事情......

有人可以帮助我吗?

提前致谢。

1 个答案:

答案 0 :(得分:4)

警告:我没有为您提供明确的解决方案,但仅仅是关于如何调试此问题的一些观察和建议[基于多年编写/调试Linux设备驱动程序的经验]

我认为你认为回调是而不是正在完成,因为你没有得到任何printk消息。但是,回调是只有 的地方。但是,printk级别设置得足够高以查看消息吗?我将dev_info添加到您的模块init,以证明它按预期打印。

此外,如果dma_start没有按预期工作,您[可能]仍然无法收到回电,因此我也会在那里添加一些dev_info来电(例如,在步骤7)中的呼叫之前和之后。我还注意到dma_start中的所有调用都没有检查错误[可能是罚款或无效返回,只是提到你错过了一个]

此时,应该注意到这里确实存在两个问题:(1)您的DMA请求是否已成功启动[和完成]? (2)你有回电吗?

因此,我将一些代码从dma_complete拆分为(例如)dma_test_done。后者执行相同的检查,但只打印&#34;完成&#34;信息。您可以在轮询模式下调用它来验证DMA完成。

所以,如果你[最终]完成了,那么问题就会减少为什么你没有得到回调。但是,如果你没有[甚至]完成,那就是一个更基本的问题。

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

在第7步,您正在拨打dma_async_issue_pending,应致电pl330_issue_pendingpl330_issue_pending会致电pl330_tasklet

pl330_tasklet tasklet 函数,但也可以直接调用[在没有活动请求时启动DMA]。

pl330_tasklet将循环播放&#34;工作&#34;排队并将任何已完成的项目移至其已完成的项目#34;队列。然后它尝试启动新请求。然后它在其完成的队列上循环并发出回调。

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

当你拨打电话时,一切都可能很忙,所以没有已完成的请求,没有空间来启动新请求,所以没有完成。在这种情况下,稍后将再次调用pl330_tasklet

因此,当pl330_tasklet返回时,没有可能已经发生尚未。这很可能是你的情况。

dma_async_issue_pending尝试通过调用pl330_tasklet来启动新的DMA。它将通过查看fill_queue来检查描述符是否[已经]忙碌。因此,您可能希望验证您的值是否正确。否则,您从不获得回调[甚至任何DMA启动]。

然后,status != BUSY将尝试通过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为许多其他请求提供服务,您可以通过检查设备的私有数据指针是否与您的匹配来限制调试消息。

特别是,您希望在请求实际开始时收到消息,因此您可以在pl330.c

的末尾添加调试消息

然后,在pl330_submit_req内为请求添加消息也会有所帮助。

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

<强>更新

  

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

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

当你打电话给(1)pl330_tasklet时,(2)它会调用dma_async_is_tx_complete,(3)chan->device->device_tx_status,(4)调用pl330_tx_status }

旁注/小费:当我跟踪此消息时,我只是在dma_cookie_statusdmaengine.h之间来回翻转。它就像:看(1),它叫(2)。那套在哪里?在pl330.c中,我推测。所以,我抓住了字符串并获得了pl330的功能名称(即(3))。所以,我去那里,看到它确实如此(4)。所以...回到pl330.c ...

但是,当您进行外部呼叫时,您将忽略[设置为NULL]后两个参数。这些可能很有用,因为它们会返回&#34; last&#34;和&#34;使用&#34;饼干。因此,即使您没有完全完成,这些值也可能会发生变化并显示出部分进展。

其中一个最终应该>&#34;返回&#34;饼干价值。 (即)整个操作应该完成。因此,这将有助于区分可能发生的事情。

另请注意,dmaengine.h位于dmaengine.h下方,dma_async_is_tx_complete。此函数根据您传递的Cookie值以及&#34; last&#34;决定是返回dma_async_is_complete还是DMA_COMPLETE。和&#34;使用&#34; cookie值。它是被动的,而不是在代码路径[AFAICT]中使用,但它确实显示了如何自己计算完成。

相关问题