首页 > 其他分享 >DMA Engine框架(一)

DMA Engine框架(一)

时间:2024-03-24 22:33:27浏览次数:34  
标签:Engine DMA struct 框架 dma chan tx addr

参考: 《Linux设备驱动开发》 http://www.wowotech.net/linux_kenrel/dma_engine_overview.html https://blog.csdn.net/yangguoyu8023/article/details/121852348 https://www.byteisland.com/dma-%E4%B8%8E-scatterlist-%E6%8A%80%E6%9C%AF%E7%AE%80%E4%BB%8B/ https://blog.csdn.net/weixin_41028621/article/details/102757088   DMA基础概念可查看:https://www.cnblogs.com/lethe1203/p/18092378   一个完整的 DMA 传输过程必须经过 DMA 请求、DMA 响应、DMA 传输、DMA 结束这四个阶段。 1、DMA 请求:CPU 对 DMA 控制器初始化,并向 I/O 接口发出操作命令,I/O 接口提出 DMA 请求 2、DMA 响应:DMA 控制器对 DMA 请求判别优先级以及屏蔽位,向总线裁决逻辑提出总线请求,当 CPU 执行完成当前的总线周期之后即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示 DMA 已经就绪,通过 DMA 控制器通知 I/O 接口开始 DMA 传输。 3、DMA 传输:在 DMA 控制器的引导下,在存储器和外设之间进行数据传送,在传送过程中不需要 CPU 的参与 4、DMA 结束:当完成既定操作之后,DMA 控制器释放总线控制权,并向 I/O 接口发出结束信号,当 I/O 接口收到结束信号之后,一方面停止 I/O 设备的工作,另一方面向 CPU 提出中断请求,使 CPU 从不介入状态解脱,并执行一段检查本次 DMA 传输操作正确性的代码。最后带着本次操作的结果以及状态继续执行原来的程序。   DMA Engine是开发DMA控制器驱动程序的通用内核框架。DMA的主要目的是在复制内存的时候减轻CPU的负担。使用通道将事务(I/O数据传输)委托给DMA Engine,DMA Engine通过其驱动程序API提供一组可供其他设备(从设备)使用的通道,如下: 0 DMA Engine引用的头文件如下:

#include <linux/dmaengine.h>
 

Slave-DMA和Async TX api:

Linux为了方便基于DMA的memcpy、memset等操作,在dma engine之上,封装了一层更为简洁的API,这些API就是Async TX API 0 最后,因为memory到memory的DMA传输有了比较简洁的API,没必要直接使用dma engine提供的API,最后就导致dma engine所提供的API就特指为Slave-DMA API(把mem2mem剔除了)。Slave-DMA中的“slave”,指的是参与DMA传输的设备。而对应的,“master”就是指DMA controller自身。一定要明白“slave”的概念,才能更好的理解kernel dma engine中有关的术语和逻辑。 这些也会在下面的描述中具体体现  

从设备DMA用法:

从设备DMA用法不复杂,具体过程如下: 1、分配DMA从通道 2、设备从设备和DMA控制器特定参数 3、获取事务的描述符 4、提交事务 5、发出挂起的请求并等待回调通知  

分配DMA从通道:

使用dma_request_channel()请求通道,原型如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t *mask,
                                     dma_filter_fn filter_fn,
                                     void *filter_param);

// 参数说明:
mask: 用于指定请求 DMA 通道的能力掩码,可以通过宏 DMA_SLAVE、DMA_MEMCPY 等进行设置。
filter_fn: 是一个回调函数,用于过滤 DMA 通道,如果返回值为真,表示该通道符合要求。
filter_param: 被传递给 filter_fn 函数作为参数。
// 返回值说明:
如果成功找到符合条件的 DMA 通道,则返回一个指向 struct dma_chan 结构的指针。
如果没有找到符合条件的 DMA 通道,则返回 NULL。
mask是位图掩码,表示该通道必须满足的功能。使用它主要是为了指定驱动程序需要执行的传输类型:
enum dma_transaction_type {
    DMA_MEMCPY,               // 内存拷贝操作
    DMA_XOR,                  // 异或操作
    DMA_PQ,                   // 奇偶校验操作
    DMA_XOR_VAL,              // 带有异或值的操作
    DMA_PQ_VAL,               // 带有奇偶校验值的操作
    DMA_MEMSET,               // 内存设置操作
    DMA_MEMSET_SG,            // 散列表内存设置操作
    DMA_INTERRUPT,            // 中断相关的 DMA 操作
    DMA_PRIVATE,              // 私有的 DMA 操作
    DMA_ASYNC_TX,             // 异步传输的 DMA 操作
    DMA_SLAVE,                // 从设备相关的 DMA 操作
    DMA_CYCLIC,               // 循环的 DMA 操作
    DMA_INTERLEAVE,           // 交错的 DMA 操作
    DMA_COMPLETION_NO_ORDER,  // 无序完成的 DMA 操作
    DMA_REPEAT,               // 重复的 DMA 操作
    DMA_LOAD_EOT,             // 加载结束操作标志的 DMA 操作
    DMA_TX_TYPE_END,          // 最后一个事务类型,用于创建功能掩码
};
dma_cap_zero()和dma_cap_set()函数用于清除掩码,设置所需要的功能,例如:
dma_cap_mask mask_test;
struct dma_chan *chan;
dma_cap_zero(mask_test);
dma_cap_set(DMA_MEMCPY, mask_test);
chan = dma_request_channel(mask_test, NULL, NULL);
前面提到的dma_filter_fn,dma_filter_fn 是一个指向返回布尔类型的函数的指针,定义如下:
typedef bool (*dma_filter_fn)(struct dma_chan *chan, void *filter_param);

// 参数说明
struct dma_chan *chan:表示 DMA 通道的指针,函数可以使用这个通道来检查和处理 DMA 请求。
void *filter_param:一个指向参数的指针,可以用来传递额外的过滤参数给 DMA 过滤器函数。
如果filter_fn参数为NULL,则dma_request_channel()将只返回满足功能掩码的第一个通道。否则,当掩码参数不足以指定所需通道时,可以使用filter_fn例程作为系统中可用通道的过滤器。内核为系统中每个空闲通道调用一次filter_fn例程。当看到合适的通道,filter_fn应该返回DMA_ACK,它将把给定的通道标记为dma_request_channel()的返回值。 通过此接口分配的通道由调用者独占,直到调用dma_release_channel()为止:
void dma_release_channel(struct dma_chan *chan)
 

设置从设备和控制器指定参数:

数据结构struct dma_slave_config,结构体如下
 struct dma_slave_config {
         enum dma_transfer_direction direction;    // 传输方向
         phys_addr_t src_addr;                        // 源地址
         phys_addr_t dst_addr;                        // 目的地址
         enum dma_slave_buswidth src_addr_width;
        // 源位宽
         enum dma_slave_buswidth dst_addr_width;        // 目的位宽
         u32 src_maxburst;                               // 一次burst可以发送到设备的最大字数(单位为dma_slave_buswidth)
         u32 dst_maxburst;
         u32 src_port_window_size;
         u32 dst_port_window_size;
         bool device_fc;
         unsigned int slave_id;
         void *peripheral_config;
         size_t peripheral_size;
 };

 enum dma_transfer_direction {
         DMA_MEM_TO_MEM,
         DMA_MEM_TO_DEV,
         DMA_DEV_TO_MEM,
         DMA_DEV_TO_DEV,
         DMA_TRANS_NONE,
 };
  
  enum dma_slave_buswidth {
         DMA_SLAVE_BUSWIDTH_UNDEFINED = 0,
         DMA_SLAVE_BUSWIDTH_1_BYTE = 1,
         DMA_SLAVE_BUSWIDTH_2_BYTES = 2,
         DMA_SLAVE_BUSWIDTH_3_BYTES = 3,
         DMA_SLAVE_BUSWIDTH_4_BYTES = 4,
         DMA_SLAVE_BUSWIDTH_8_BYTES = 8,
         DMA_SLAVE_BUSWIDTH_16_BYTES = 16,
         DMA_SLAVE_BUSWIDTH_32_BYTES = 32,
         DMA_SLAVE_BUSWIDTH_64_BYTES = 64,
 };    
例如:
dma_cap_mask my_dma_cap_mask;
struct dma_chan *chan;
dma_cap_zero(my_dma_cap_mask);
dma_cap_set(DMA_MEMCPY, my_dma_cap_mask);

struct dma_chan *my_dma_chan;
dma_addr_t dma_src, dma_dst;
struct dma_slave_config my_dma_cfg = {0};

my_dma_chan = dma_request_channel(my_dma_cap_mask, 0, NULL);
my_dma_cfg.direction = DMA_MEM_TO_MEM;
my_dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_32_BYTES;

dmaengine_slave_config(my_dma_chan, my_dma_cfg);

char *rx_data, *tx_data;
rx_data = kzalloc(BUFFER_SIZE, GFP_DMA);
tx_data = kzalloc(BUFFER_SIZE, GFP_DMA);

feed_data(tx_data);

dma_src_addr = dma_map_single(NULL, tx_data, BUFFER_SIZE, DMA_MEM_TO_MEM);
dma_dst_addr = dma_map_single(NULL, rx_data, BUFFER_SIZE, DMA_MEM_TO_MEM);
上面的代码中,调用dma_request_channel()函数获取DMA通道,dmaengine_slave_config()来应用配置,调用dma_map_single映射rx和tx缓冲区,以便在DMA中使用  

获取事务描述符:

 struct dma_chan {
         struct dma_device *device;
         struct device *slave;
         dma_cookie_t cookie;
         dma_cookie_t completed_cookie;

         /* sysfs */
         int chan_id;
         struct dma_chan_dev *dev;
         const char *name;
 #ifdef CONFIG_DEBUG_FS
         char *dbg_client_name;
 #endif

         struct list_head device_node;
         struct dma_chan_percpu __percpu *local;
         int client_count;
         int table_count;

         /* DMA router */
         struct dma_router *router;
         void *route_data;

         void *private;
 };
dma_chan结构体包含dma_device结构体,他表示提供该通道的DMA设备(实际为控制器)。此控制器的内核驱动程序负责提供一组函数来准备DMA事务,其中每一个函数都对应着DMA事务类型。根据事务类型的不同,可能只能选择专用函数,一些函数类型如下:
struct dma_async_tx_descriptor *dma_prep_dma_memcpy(struct dma_chan *chan,
                                                    dma_addr_t dest,
                                                    dma_addr_t src,
                                                    size_t len,
                                                    unsigned long flags);
描述:准备一个用于内存拷贝的 DMA 请求。
参数:
struct dma_chan *chan:DMA 通道。
dma_addr_t dest:目的地址。
dma_addr_t src:源地址。
size_t len:拷贝的数据长度。
unsigned long flags:标志位,如 DMA_CTRL_ACK、DMA_PREP_INTERRUPT 等。

struct dma_async_tx_descriptor *dma_prep_dma_xor(struct dma_chan *chan,
                                                 dma_addr_t dest,
                                                 dma_addr_t src1,
                                                 dma_addr_t src2,
                                                 size_t len,
                                                 unsigned long flags);
描述:准备一个用于内存异或的 DMA 请求。
参数:
struct dma_chan *chan:DMA 通道。
dma_addr_t dest:目的地址。
dma_addr_t src1:源地址1。
dma_addr_t src2:源地址2。
size_t len:异或的数据长度。
unsigned long flags:标志位,如 DMA_CTRL_ACK、DMA_PREP_INTERRUPT 等。

struct dma_async_tx_descriptor *dma_prep_dma_sg(struct dma_chan *chan,
                                                struct scatterlist *sgl,
                                                unsigned int sg_len,
                                                enum dma_transfer_direction direction,
                                                unsigned long flags);
描述:准备一个用于散列表传输的 DMA 请求。
参数:
struct dma_chan *chan:DMA 通道。
struct scatterlist *sgl:散列表的起始地址。
unsigned int sg_len:散列表的长度。
enum dma_transfer_direction direction:数据传输方向。
unsigned long flags:标志位,如 DMA_CTRL_ACK、DMA_PREP_INTERRUPT 等。

等等
以imx-sdma.c为例,上面这些函数都会返回指向struct dma_async_tx_descriptor结构体的指针,其对应于事务描述符。比如,内存到内存的复制,使用device_prep_dma_memcpy:
struct dma_device *dma_dev = my_dma_chan->device;
struct dma_async_tx_descriptor *tx = NULL;

tx = dma_dev->dma_prep_dma_memcpy(my_dma_chan, dma_dst_addr, dma_src_addr, BUFFER_SIZE, 0);
实际上,应该使用dmaengine_prep_* DMA Engine的操作API,但是请注意,这些函数在内部执行前面刚执行过的操作。例如,对于内存到内存的复制,可以使用device_prep_dma_memcpy()函数
struct dma_async_tx_descriptor *(* device_prep_dma_memcpy)(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, size_t len, unsigned long flags)
这个例子变为:
struct dma_async_tx_descriptor *tx = NULL;
tx = dma_dev->device_prep_dma_memcpy(my_dma_chan, dma_dst_addr, dma_src_addr, BUFFER_SIZE, 0);
 

提交事务:

要将事务放入驱动程序的等待队列中,需要调用dmaengine_submit(),一旦准备好描述符并添加回调信息,就应该将其放在DMA Engine驱动程序等待队列中:
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *tx);
该函数返回一个cookie,可以通过其他DMA Engine检查DMA活动的进度。dmaengine_submit()不会启动DMA操作,它只是将其添加到待处理的队列中。下面将讨论启动事务:
struct completion transfer_ok;
init_completion(&transfer_ok);
tx->callback = my_dma_callback;

dma_cookie_t cookie = dmaengine_submit(tx);

if(dma_submit_error(cookie)) {
    ...
}
...
 

发布待处理DMA请求并等待回调通知:

启动事务是DMA传输设置的最后一步。在通道上调用dma_async_issue_pending()来激活通道待处理队列中的事务。如果通道空闲,则队列中的第一个事务将启动,后续事务排队等候。DMA操作完成时,队列的下一个事务启动。并触发软中断tasklet。如果已经设置,则该tasklet负责调用客户端驱动程序的完成回调函数进行通知,如下:
dma_async_issue_pending(my_dma_chan);
wait_for_completion(&transfer_ok);

dma_unmap_single(my_dma_chan->device->dev, dma_src_addr, BUFFER_SIZE, DMA_MEM_TO_MEM);
dma_unmap_single(my_dma_chan->device->dev, dma_dst_addr, BUFFER_SIZE, DMA_MEM_TO_MEM);

/* 通知rx_data和tx_data虚拟地址处理缓冲区 */
  实际上发布待处理事务的DMA Engine API函数是dmaengine_issue_pending(struct dma_chan *chan),它是dma_async_issue_pending()的包装。          

标签:Engine,DMA,struct,框架,dma,chan,tx,addr
From: https://www.cnblogs.com/lethe1203/p/18093243

相关文章

  • SC2440 DMA使用
    DMA介绍可见:https://www.cnblogs.com/lethe1203/p/18092378本节复习2440DMA使用参考资料:韦东山驱动第二期 DMA字符驱动代码:#include<linux/module.h>#include<linux/kernel.h>#include<linux/fs.h>#include<linux/init.h>#include<linux/delay.h>#incl......
  • python自动化——web自动化框架常用封装代码复习——当你会开发之后,发现一切都是如此
    PS:  PO模式知识点如下: 1、知识点:函数的书写、类、继承,模块导入; 2、思路:分层,抽离;     =====================================================================          编写用例基础版本:   pytest参数化,以及原始selenium用例编......
  • DMA一致性映射使用
    DMA映射可参考:https://www.cnblogs.com/lethe1203/p/18092749 reserved_memory方式1、定义保留内存的节点,由设备节点使用test_device_reserved:pmu_noc@0xe8000000{compatible="shared-dma-pool";reg=<0x00xe80000000x00x800000>;......
  • DMA映射
    参考资料:https://elinux.org/images/3/32/Pinchart--mastering_the_dma_and_iommu_apis.pdf《宋宝华:Linux设备驱动开发详解》 VA和PA的相互转换:系统启动时,MMU便会建立映射表,将DRAM物理地址和虚拟地址进行映射,在linux内核中,可以使用下面的函数完成VA和PA的转换#definevir......
  • django框架简介
    【一】python主流web框架//django大而全自带的功能非常的多但是有时候会略显笨重//flask小而精自带的功能非常的少但是第三方模块非常的多类似于'游骑兵'flask的第三方模块加到一起甚至比django还多并且也越来越像djangoflask由于过多的依赖于第三方模块有时候也......
  • 手动搭建web框架
    Web框架本质web框架本质上就是一个socket服务端,但是它的功能非常强大用户的浏览器可以看作一个拥有可视化界面的socket客户端两者通过网络请求完成数据交互手撸Web框架【1】原始版本web框架可以是自己写的一个简陋的socket服务端importsocketserver=socket.socket......
  • django框架三板斧
    【一】Django项目如何添加新功能【1】添加URL映射在项目的urls.py中,通过导入相应的应用app的views在urlpatterns列表中添加对应的映射如fromdjango.contribimportadminfromdjango.urlsimportpathfromuserimportviewsurlpatterns=[path('admin/',admin......
  • macbook使用php的fastadmin框架验证码不显示解决办法
    macbook使用php的fastadmin框架验证码不显示解决办法给php安装freetype插件freetype下载链接解压插件进入命令行工具,然后进到刚才解压的文件目录中然后执行该命令./configure--enable-static--enable-shared(没有指定prefix,.h文件默认安装到/usr/local/includ......
  • DMA cache一致性
    本节内容参考《宋宝华:Linux设备驱动开发详解》 cache和DMA本身似乎是两个毫无关联的事物。cache被用作CPU针对内存的缓存,利用程序的空间局部性和时间局部性原理,达到较高的命中率,从而避免CPU每次都必须要与相对慢速的内存交互数据来提高数据的访问速率。DMA可以作为内存与外设之......
  • 前端框架之Bootstrap
    一、什么是BootstrapBootstrap是一个用于快速开发Web应用程序和网站的前端框架。Bootstrap是基于HTML、CSS、JAVASCRIPT的。Bootstrap是一个流行的开源前端框架,用于快速构建响应式和移动优先的网站和Web应用程序。它由Twitter的开发人员创建,旨在帮助开发人员快速搭建......