当前位置: 首页 > news >正文

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;}// 申请dmaif (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内核》---宋宝华编著

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【Qt笔记】QToolButton控件详解
  • 有哪些内部知识库类似钉钉,满足企业多样化需求?
  • 服务器(百度云)部署项目(jar包)
  • 基于spring Boot的网上报修平台的设计和实现---附源码94800
  • 深度学习学习经验——全连接神经网络(FCNN)
  • 国内外大模型汇总:Open AI大模型、Google大模型、Microsoft大模型、文心一言大模型、通义千问大模型、字节豆包大模型、智普清言大模型
  • Aiseesoft Data Recovery for Mac:专业级数据恢复解决方案
  • 淘宝API接口解析: item_fee获取淘宝商品运费接口
  • 动态代理和静态代理的区别,动态代理怎么提高网络安全性
  • vue中使用vue-video-player插件播放视频 以及 audio播放音频
  • 简单工作流(后端部分-spring boot,顺便优化了下ui)
  • 系统架构设计师——架构风格
  • 低成本、高精度电子计量解决方案
  • yolov7详解
  • 【设计模式-单例】
  • [译] React v16.8: 含有Hooks的版本
  • android图片蒙层
  • go语言学习初探(一)
  • Java,console输出实时的转向GUI textbox
  • JSONP原理
  • vue-loader 源码解析系列之 selector
  • 爱情 北京女病人
  • 工作手记之html2canvas使用概述
  • 简单实现一个textarea自适应高度
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 跳前端坑前,先看看这个!!
  • 用 Swift 编写面向协议的视图
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • ​​​【收录 Hello 算法】10.4 哈希优化策略
  • ​ArcGIS Pro 如何批量删除字段
  • ###C语言程序设计-----C语言学习(3)#
  • (PADS学习)第二章:原理图绘制 第一部分
  • (pt可视化)利用torch的make_grid进行张量可视化
  • (八)五种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (回溯) LeetCode 77. 组合
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (五十)第 7 章 图(有向图的十字链表存储)
  • (译) 函数式 JS #1:简介
  • (转)winform之ListView
  • (转)德国人的记事本
  • (转)我也是一只IT小小鸟
  • .Net 6.0 处理跨域的方式
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET Core WebAPI中封装Swagger配置
  • .Net Core中Quartz的使用方法
  • .NET设计模式(8):适配器模式(Adapter Pattern)
  • .NET正则基础之——正则委托
  • .project文件
  • /etc/shadow字段详解
  • @SuppressWarnings(unchecked)代码的作用
  • @transactional 方法执行完再commit_当@Transactional遇到@CacheEvict,你的代码是不是有bug!...
  • [ C++ ] STL_list 使用及其模拟实现
  • [ IO.File ] FileSystemWatcher