繁体   English   中英

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

[英]Linux DMA: Using the DMAengine for scatter-gather transactions

我尝试使用自定义内核驱动程序中的DMAengine API来执行分散 - 收集操作。 我有一个连续的存储区域作为源极和我想通过一个散布表结构中几个分布式缓冲器复制其数据。 DMA控制器是支持DMAengine API的PL330(参见PL330 DMA控制器 )。

我的测试代码如下:

在我的驱动程序头文件( 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从不调用我的回调函数,我不知道它为什么会发生。 也许,我误解了一些事情......

谁能帮助我?

提前致谢。

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

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

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

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

所以,我将dma_complete一些代码dma_complete为(例如) dma_test_done 后者执行相同的检查,但只打印“完整”消息。 您可以在轮询模式下调用此方法来验证DMA完成。

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

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

在步骤7,您正在调用dma_async_issue_pending ,它应该调用pl330_issue_pending pl330_issue_pending将调用pl330_tasklet

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

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

pl330_tasklet获取回调指针,但如果它为null,则会被静默忽略。 你已经设置了一个回调,但是验证设置回调的地方可能是好的,或者传播到pl330_tasklet将从中获取回调的位置。

当您拨打电话时,一切都可能很忙,因此没有完成的请求, 没有空间来启动新请求,因此无需完成任何操作。 在这种情况下, pl330_tasklet将再次调用pl330_tasklet

所以,当dma_async_issue_pending回报,可能无任何 尚未发生。 这很可能适用于您的情况。

pl330_tasklet尝试通过调用fill_queue来启动新的DMA。 它将通过查看status != BUSY来检查描述符是否[已经]忙碌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)它调用dma_async_is_tx_complete chan->device->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]中使用,但它确实显示了如何自己计算完成。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM