Linux 流式DMA映射(DMA Streaming Mapping)

流式DMA相关的接口为dma_map_sg(),dma_unmap_sg(),dma_map_single(),dma_unmap_single()。流式DMA一般用于已经分配好的内存,然后再对其进行DMA操作,而不是提前申请好一块cache一致性的内存给DMA用。例如从协议栈里发下来的一个包,想通过网卡发送出去。但是协议栈并不知道这个包要往哪里走,因此分配内存的时候并没有特殊对待,这个包所在的内存通常都是可以cache的。这时,内存在给DMA使用之前,就要调用一次dma_map_sg()或dma_map_single(),取决于你的DMA引擎是否支持聚集散列(DMA scatter-gather),支持就用dma_map_sg(),不支持就用dma_map_single()。DMA用完之后要调用对应的unmap接口。

相关例程源码可以通过以下链接免费下载:

https://download.csdn.net/download/slov8/89668736

1、SG DMA

先介绍一下什么是SG(scatter-gather)DMA,翻译过来就是分散聚集DMA,SG-DMA应该算是流式DMA的一种。进行一次DMA数据搬运的一个前提是这段DMA内存的物理地址必须是连续的,DMA搬运完就会产生一次中断(前提是开启中断的情况下),如果有十个不同区域的DMA传输,就需要产生10次中断,这样效率明显不高。SG DMA可以把这十个不同区域的DMA串起来,一次性地把这十个DMA区域都传输完再产生一次中断。直白点说就是把分散的几个DMA区域都聚集到一起,一起打包传输,所以也叫集散DMA。

结合笔记的开发经历,Linux在用户层申请的内存一般都是物理地址不连续,用户层申请的地址传递到内核后,在内核中把地址转换成内存页,页是物理地址的, 然后用这些页去做SG DMA映射,就可以使用户层申请的物理地址不连续的内存做DMA操作了。后面笔者会给出实际例程。笔者认为每写一篇文章都要经过自己的实际操作,然后总结,还要有自己的理解,这样写文章才用意义......扯远了,接着往下看吧。

1.1、重要的结构体

struct scatterlist {
#ifdef CONFIG_DEBUG_SG
    unsigned long   sg_magic;
#endif
    unsigned long   page_link;
    unsigned int    offset;
    unsigned int    length;
    dma_addr_t  dma_address;
#ifdef CONFIG_NEED_SG_DMA_LENGTH
    unsigned int    dma_length;
#endif
};

dma_addrss:DMA设备能操作到的虚拟地址即IOVA;

dma_length:DMA设备通过它可以知道数据区的长度;
page_link:CPU看到的虚拟地址即VA;
length:CPU能看到的数据长度;
offset:数据在页中的偏移;
单个scattherlist对应单个内存页;

单个struct scatterlist和一个页的对应关系如图:

1.2、重要的API函数

sg_alloc_table:用于申请struct scatterlist类型的数组,并初始化;
sg_free_table:释放struct scatterlist类型的数组;
dma_map_sg:对应scatterlist进行dma映射;
dma_unmap_sg:释放dma映射;
get_user_pages_fast:锁定用户层申请的内存,并返回对应的内存页地址,需要通过put_page释放;
get_kernel_pages:锁定内核申请的内存,并返回对应的内存页地址,需要通过put_page释放;

2、流式DMA例程

这里以散集表(scatter-gather)dma为例,并且使用sg dma来实现memcpy的功能,包括以下几种类型:

实际的dma操作一般都是内存到外设,或者外设到内存的传输,此例程使用独立的dma控制器来进行内存到内存的传输,操作流程都是一样的。此例程只能在arm结构上的CPU运行,在X86上无法运行,因为X86平台的CPU没有独立的DMA控制器。笔记在正点原子RK3568开发板上面是可以跑成功的。

2.1、工作流程

以数据传输方向: 用户缓冲区->SG DMA->内核缓冲区 为例:

以下是最核心的传输函数:

static int sg_dma_xfer_submit(struct user_sg_dma_io *src_io, struct user_sg_dma_io *dst_io, struct user_sg_dma_dev *dev, enum SG_DMA_MEMCPY_TYPE memcpy_type)
{
    int ret = 0;
    int i;
    struct scatterlist *src_sg;
    struct sg_table *src_sgt;
    struct scatterlist *dst_sg;
    struct sg_table *dst_sgt;
    struct dma_chan     *dma_chan;
    struct dma_device   *dma_dev;
    enum dma_ctrl_flags     flags;
    enum dma_status     status;
    dma_cookie_t        cookie;
    dma_addr_t src_addr;
    dma_addr_t dst_addr;
    unsigned int dst_len;
    unsigned int src_len;
    struct dma_async_tx_descriptor *tx = NULL;

    if (memcpy_type == SG_DMA_MEMCPY_USER2KERNEL || memcpy_type == SG_DMA_MEMCPY_KERNEL2USER ||
        memcpy_type == SG_DMA_MEMCPY_KERNEL2KERNEL) {
        ret = prepare_kernel_sg_dma_buf(dev);
        if (ret) {
            return ret;
        }
        sg_dma_init_kernel_srcs(src_io->kernel_buf, src_io->kernel_buf_len);
        sg_dma_init_kernel_dsts(dst_io->kernel_buf, dst_io->kernel_buf_len);
    }

    // 分配dma通道
    ret = request_channels(dev, DMA_MEMCPY);
    if (ret) {
        return ret;
    }

    // 申请dma
    if (memcpy_type == SG_DMA_MEMCPY_USER2KERNEL || memcpy_type == SG_DMA_MEMCPY_KERNEL2USER ||
        memcpy_type == SG_DMA_MEMCPY_USER2USER) {
        ret = alloc_user_sg_dma(dev);
        if (ret) {
            return ret;
        }
    }
    if (memcpy_type == SG_DMA_MEMCPY_USER2KERNEL || memcpy_type == SG_DMA_MEMCPY_KERNEL2USER ||
        memcpy_type == SG_DMA_MEMCPY_KERNEL2KERNEL) {
        ret = alloc_kernel_sg_dma(dev);
        if (ret) {
            return ret;
        }
    }
    src_sg = src_io->sgt.sgl;
    src_sgt = &src_io->sgt;
    dst_sg = dst_io->sgt.sgl;
    dst_sgt = &dst_io->sgt;
    flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
    dma_chan = dev->chan;
    dma_dev = dma_chan->device;

    for (i = 0; i < src_sgt->nents; i++, src_sg = sg_next(src_sg), dst_sg = sg_next(dst_sg)) {
        src_len = sg_dma_len(src_sg);
        src_addr = sg_dma_address(src_sg);
        dst_len = sg_dma_len(dst_sg);
        dst_addr = sg_dma_address(dst_sg);
        tx = dma_dev->device_prep_dma_memcpy(dma_chan,
                            dst_addr, src_addr, dst_len, flags);
        if (!tx) {
            pr_err("[%s]get dma tx descriptor failed.\n", __func__);
            ret = -1;
            break;
        }
        dev->done = false;
        tx->callback = dma_memcpy_callback;
        tx->callback_param = dev;
        cookie = tx->tx_submit(tx);
        if (dma_submit_error(cookie)) {
            pr_err("[%s]dma submit error.\n", __func__);
            ret = -1;
            break;
        }
        dma_async_issue_pending(dma_chan);
        wait_event_freezable_timeout(dev->done_wait, dev->done, msecs_to_jiffies(dev->timeout));
        status = dma_async_is_tx_complete(dma_chan, cookie, NULL,
                            NULL);
        if (!dev->done) {
            pr_err("[%s]dma submit timeout.\n", __func__);
            ret = -1;
            break;
        }
        if (status != DMA_COMPLETE) {
            pr_err("[%s]dma no complete.\n", __func__);
            ret = -1;
            break;
        }
    }
    pr_info("[%s]release\n", __func__);
    dmaengine_terminate_sync(dma_chan);
    char_user_sgdma_release(dev);
    char_kernel_sgdma_release(dev);
    release_kernel_sg_dma_buf(dev);
    dma_release_channel(dma_chan);
    // 要释放掉dma映射才能校验通过,是因为cache一致性问题,调用dma_unmap_sg可以使cache和内存重新刷新,使其保持一致
    if (!ret) {
        // 验证数据 只验证内核缓冲区的数据
        if (memcpy_type == SG_DMA_MEMCPY_KERNEL2USER || memcpy_type == SG_DMA_MEMCPY_KERNEL2KERNEL) {
            // pr_info("[%s]: verifying source buffer...\n", __func__);
            ret = dmatest_verify(src_io->kernel_buf, src_io->kernel_buf_len, PATTERN_SRC | PATTERN_COPY);
        }
        if (memcpy_type == SG_DMA_MEMCPY_USER2KERNEL || memcpy_type == SG_DMA_MEMCPY_KERNEL2KERNEL) {
            // pr_info("[%s]: verifying dest buffer...\n", __func__);
            ret = dmatest_verify(dst_io->kernel_buf, dst_io->kernel_buf_len, PATTERN_SRC | PATTERN_COPY);
        }
    }

    return ret;
}

以上代码大部分都是参考了内核驱动kernel/drivers/dma/dmatest.c,以及Xilinx的xdma驱动(dma_ip_drivers-2019.2/XDMA/linux-kernel/xdma)代码,并在此基础上修改而来的。所以说很多轮子都不需要自己造,你直接参考拿过来用就行,前提是你能完全看懂它们hahhh.....,这需要一定的功底。

完整的代码可以通过以下链接免费获取,免费!免费!不需要你的积分或者钱!其中包括内核的驱动代码,用户层的测试代码。
https://download.csdn.net/download/slov8/89668736

2.2、遇到的问题

还是遇到了Cache一致性问题。

现象:在dma传输结束之后,释放dma映射之前,进行数据校验dmatest_verify会失败。

原因:在DMA传输结束后,在调用dma_unmap_sg之前,dcache和内存的数据是不一致的,所以cpu读取的数据是旧的数据。所以一定要在dma_unmap_sg之后,相应的dcache会被置无效,cpu从cache读到数据才是正确的。

解决方法:在调用dma_unmap_sg之后,cpu再去操作数据。

3、总结

流式dma适用于在已经分配好内存的情况下,再进行dma操作,cache的一致性问题由流式dma的API函数保证。

使用流式dma映射保证cache一致性的前提是在dma传输结束之后,还要把dma映射释放掉,cpu再去访问相应的数据缓冲区。

4、参考资料

linux kernel-4.19的内核驱动代码kernel/drivers/dma/dmatest.c

Xilinx的xdma驱动(dma_ip_drivers-2019.2/XDMA/linux-kernel/xdma)代码

《Linux设备驱动开发详解-基于最新的Linux4.0内核》---宋宝华编著

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值