Linux 流式DMA映射(DMA Streaming Mapping)

news2024/11/14 6:20:08

流式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内核》---宋宝华编著

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073960.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day41| 01背包问题一 01背包问题二(滚动数组篇)416. 分割等和子集 1049.最后一块石头的重量II 494. 目标和 474. 一和零

文章目录 背景介绍01背包问题一思路方法一方法二01背包问题二(滚动数组篇)思路方法一方法二416. 分割等和子集思路方法一1049.最后一块石头的重量II思路方法一494. 目标和思路方法方法二 回溯法474. 一和零思路方法总结由于笔试的时候会判重,而这里面的代码都是我自己写的,…

不同场景下的负载均衡器

负载均衡主要用于分配来自互联网或局域网的请求或任务负载到多个服务器中。 这样做可以避免任何单个服务器的过载&#xff0c;提高响应速度&#xff0c;增加系统的整体处理能力&#xff0c;并确保系统的高可用性和可靠性。 负载均衡器大概可以分为 3 类&#xff0c;包括&#…

Argo/BGC-Argo数据下载

BGC-Argo官方网站 网址&#xff1a; https://biogeochemical-argo.org/data-access.php 信息&#xff1a;提供BGC-Argo位置及剖面预览 数据ftp下载&#xff1a;ftp://ftp.ifremer.fr/ifremer/argo 或者 ftp://usgodae.org/pub/outgoing/argo 问题&#xff1a;dac文件按照数据中…

Image Stride(内存图像行跨度)

When a video image is stored in memory, the memory buffer might contain extra padding bytes after each row of pixels. The padding bytes affect how the image is store in memory, but do not affect how the image is displayed. 当视频图像存储在内存时&#xff0…

EVE-NG安装部署使用

EVE-NG安装部署使用 一、EVE的虚拟化安装1、下载EVE-NG(社区版)2、导入虚拟机-配置-登录二、EVE中设备的连接sercureCRT连接wireshark连接一、EVE的虚拟化安装 1、下载EVE-NG(社区版) 官网下载地址(科学上网): https://www.eve-ng.net/index.php/download/ 中文网下载…

基于python django的图书数据分析系统,包括图书推荐和可视化大屏分析,带有后台

研究背景 随着数字化技术的发展&#xff0c;图书管理与数据分析在图书馆和在线图书销售平台中变得越来越重要。传统的图书管理方式通常只关注图书的借阅和归还&#xff0c;忽视了数据分析在图书管理中的潜力。通过对图书借阅、购买、和用户偏好等数据的分析&#xff0c;能够深入…

数论之组合数

组合数1&#xff1a; 预处理每一个组合数的大小 类似于dp&#xff0c;从a个苹果里面选b个出来&#xff1a;首先从a个苹果里面拿出来一个&#xff0c;这样就分成了两种&#xff0c;一种是包括这个拿出来的苹果的方案数&#xff0c;此时就只需要拿b-1个苹果。一种是不包括这种苹…

嵌入式笔记:半加器与全加器

一&#xff0c;门电路 本文使用digital软件中的双掷继电器来实现以下的门电路&#xff0c;并结合这些门电路实现半加器与八位全加器。 与门 当输入信号A&#xff0c;B都置高电平时&#xff0c;继电器带电具有磁性&#xff0c;将下方双刀开关吸附&#xff0c;使电路导通。在输出…

C++20中的约束与概念

类模板、函数模板和非模板函数(通常是类模板的成员)可能与约束(constraint)相关联&#xff0c;该约束指定对模板参数的要求(requirements)&#xff0c;可用于选择最合适的函数重载和模板特化。约束是使用模板时需要通过模板参数满足的条件或要求。这些要求的命名集合称为概念(c…

Ai+若依(系统接口--Swagger):04篇

Swagger&#xff0c;能够自动生成 API 的同步在线文档&#xff0c;并提供Web界面进行接口调用和测试。 可以直接去测试&#xff1a;--有的接口测试需要权限 我们可以去这样操作 F12 报错404 是因为多了个前缀 /dev-api 我们去后台删掉&#xff1a; 重启刷新&#xff1a;

day 39 代码随想录 | 打家劫舍 动态规划

198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个…

Ruby+Watir进行web UI自动化测试

1.新建工程文件 打开RubyMine&#xff0c;新建一个工程文件目录如下&#xff1a; login_mail.rb文件 # encoding:UTF-8 # frozen_string_literal: true当(/^打开谷歌浏览器&#xff0c;进入163邮箱登陆页面$/) do$driver Watir::Browser.new :chromesleep(2)$driver.window.…

裸机:串口通信

串口通信的基本原理 单工通信和双工通信 (1)单工就是单方向&#xff0c;双工就是双方同时收发&#xff0c;同时只能但方向但是方向可以改变叫半双工 (2)如果只能A发B收则单工&#xff0c;A发B收或者B发A收&#xff08;两个方向不能同时&#xff09;叫半双工&#xff0c;A发B收…

【C++】类与对象篇一

【C】类与对象篇一 一 .面向过程和面向对象初步认识二 .类的详解1.类的引入2.类的定义3.类的访问限定符及封装&#xff08;面试题&#xff09;4.类的作用域5.类的实例化6.类对象模型 三 . 结构体内存对齐规则&#xff08;面试题&#xff09;四 . this指针1.this指针的特性2.thi…

WOFOST的web应用

目录 运行可视化 参考链接 https://github.com/irripro/WOFOST_streamlit/tree/main 运行 运行主程序.py之后&#xff0c;得到一串命令 将该命令放到命令台中运行 然后会转到WOFOST的web端上 可视化 目前好像只支持欧洲区域的模拟运行

C++篇:C向C++迈进(上)

引言 C语言作为编程基石&#xff0c;其高效与直接性深受开发者喜爱。然而&#xff0c;随着软件复杂度的增加&#xff0c;C以其面向对象及高级特性成为了新的选择。我们接下来将学习C&#xff0c;从C语言迈向C。 什么是C C 是一种高级语言&#xff0c;由 Bjarne Stroustrup 于…

Docker 的简介

Docker 的简介 为什么会有 Docker环境一致性问题提高资源利用率和可移植性快速部署和伸缩简化管理和维护版本控制和回滚 Docker 的历史dotCloud 时代&#xff08;2010年前&#xff09;Docker 诞生&#xff08;2010-2013&#xff09;快速发展与开源&#xff08;2013-2014&#x…

mysql 日期字段自动填写日期 及自动更新日期

INSERT 时 自动给日期字段 添加 当前日期时间&#xff1a; 在默认里选中&#xff1a; CURRENT_TIMESTAMP UPDATE 时 自动给日期字段 更新 当前日期时间&#xff1a; 勾选&#xff1a;根据当前时间戳更新

通过VIN车架号查询车辆登记日期

我们先来介绍下什么是vin码&#xff0c;以及vin码的构成结构解析&#xff0c;汽车VIN码&#xff0c;也叫车辆识别号码&#xff0c;通俗可以理解为汽车的身份证号码。 接口介绍 通过17位vin码&#xff0c;获取到车辆的发动机号&#xff0c;初登日期&#xff0c;车辆型号等信息。…

CI/CD之Jenkins用于Linux系统的部署方式汇总

目录 一、前言 二、CI/CD的定义与核心原则 CI/CD在现代软件开发中的重要性 CI/CD与Jenkins的关系 三、Jenkins部署方式汇总 1. 独立服务器部署 &#xff08;1&#xff09;离线安装 &#xff08;2&#xff09;在线安装 2. Docker容器部署 3. Kubernetes集群部署 4. 云…