Linux DMA Engine framework(3)_dma controller驱动

本文介绍如何在 Linux Kernel DMA Engine 框架下编写 DMA 控制器驱动,包括关键数据结构、API 使用及开发步骤。适用于希望深入了解 DMA Engine 的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 前言

本文将从provider的角度,介绍怎样在linux kernel dmaengine的框架下,编写dma controller驱动。

2. dma controller驱动的软件框架

设备驱动的本质是描述并抽象硬件,然后为consumer提供操作硬件的友好接口。dma controller驱动也不例外,它要做的事情无外乎是:

1)抽象并控制DMA控制器。

2)管理DMA channel(可以是物理channel,也可以是虚拟channel,具体可参考[1]中的介绍),并向client driver提供友好、易用的接口。

3)以DMA channel为操作对象,响应client driver(consumer)的传输请求,并控制DMA controller,执行传输。

当然,按照惯例,为了统一提供给consumer的API(参考[2]),并减少DMA controller driver的开发难度(从论述题变为填空题),dmaengine framework提供了一套controller driver的开发框架,主要思路是(参考图片1):

dma驱动框架

图片1 DMA驱动框架

1)使用struct dma_device抽象DMA controller,controller driver只要填充该结构中必要的字段,就可以完成dma controller的驱动开发。

2)使用struct dma_chan(图片1中的DCn)抽象物理的DMA channel(图片1中的CHn),物理channel和controller所能提供的通道数一一对应。

3)基于物理的DMA channel,使用struct virt_dma_cha抽象出虚拟的dma channel(图片1中的VCx)。多个虚拟channel可以共享一个物理channel,并在这个物理channel上进行分时传输。

4)基于这些数据结构,提供一些便于controller driver开发的API,供driver使用。

上面三个数据结构的描述,可参考第3章的介绍。然后,我们会在第4章介绍相关的API、controller driver的开发思路和步骤以及dmaengine中和controller driver有关的重要流程。

3. 主要数据结构描述

3.1 struct dma_device

用于抽象dma controller的struct dma_device是一个庞杂的数据结构(具体可参考include/linux/dmaengine.h中的代码),不过真正需要dma controller driver关心的内容却不是很多,主要包括:

注1:为了加快对dmaengine framework的理解和掌握,这里只描述一些简单的应用场景,更复杂的场景,只有等到有需求的时候,再更深入的理解。

channels,一个链表头,用于保存该controller支持的所有dma channel(struct dma_chan,具体可参考3.2小节)。在初始化的时候,dma controller driver首先要调用INIT_LIST_HEAD初始化它,然后调用list_add_tail将所有的channel添加到该链表头中。

cap_mask,一个bitmap,用于指示该dma controller所具备的能力(可以进行什么样的DMA传输),例如(具体可参考enum dma_transaction_type的定义): 
    DMA_MEMCPY,可进行memory copy; 
    DMA_MEMSET,可进行memory set; 
    DMA_SG,可进行scatter list传输; 
    DMA_CYCLIC,可进行cyclic类[2]的传输; 
    DMA_INTERLEAVE,可进行交叉传输[2]; 
    等等,等等(各种奇奇怪怪的传输类型,不看不知道,一看吓一跳!!)。 
另外,该bitmap的定义,需要和后面device_prep_dma_xxx形式的回调函数对应(bitmap中支持某个传输类型,就必须提供该类型对应的回调函数)。

src_addr_widths,一个bitmap,表示该controller支持哪些宽度的src类型,包括1、2、3、4、8、16、32、64(bytes)等(具体可参考enum dma_slave_buswidth 的定义)。 
dst_addr_widths,一个bitmap,表示该controller支持哪些宽度的dst类型,包括1、2、3、4、8、16、32、64(bytes)等(具体可参考enum dma_slave_buswidth 的定义)。

directions,一个bitmap,表示该controller支持哪些传输方向,包括DMA_MEM_TO_MEM、DMA_MEM_TO_DEV、DMA_DEV_TO_MEM、DMA_DEV_TO_DEV,具体可参考enum dma_transfer_direction的定义和注释,以及[2]中相关的说明。

max_burst,支持的最大的burst传输的size。有关burst传输的概念可参考[1]。

descriptor_reuse,指示该controller的传输描述可否可重复使用(client driver可只获取一次传输描述,然后进行多次传输)。

device_alloc_chan_resources/device_free_chan_resources,client driver申请/释放[2] dma channel的时候,dmaengine会调用dma controller driver相应的alloc/free回调函数,以准备相应的资源。具体要准备哪些资源,则需要dma controller driver根据硬件的实际情况,自行决定(这就是dmaengine framework的流氓之处,呵呵~)。

device_prep_dma_xxx,同理,client driver通过dmaengine_prep_xxx API获取传输描述符的时候,damengine则会直接回调dma controller driver相应的device_prep_dma_xxx接口。至于要在这些回调函数中做什么事情,dma controller driver自己决定就是了(真懒啊!)。

device_config,client driver调用dmaengine_slave_config[2]配置dma channel的时候,dmaengine会调用该回调函数,交给dma controller driver处理。

device_pause/device_resume/device_terminate_all,同理,client driver调用dmaengine_pause、dmaengine_resume、dmaengine_terminate_xxx等API的时候,dmaengine会调用相应的回调函数。

device_issue_pending,client driver调用dma_async_issue_pending启动传输的时候,会调用调用该回调函数。

总结:dmaengine对dma controller的抽象和封装,只是薄薄的一层:仅封装出来一些回调函数,由dma controller driver实现,被client driver调用,dmaengine本身没有太多的操作逻辑。

3.2 struct dma_chan

struct dma_chan用于抽象dma channel,其内容为:

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

        /* sysfs */ 
        int chan_id; 
        struct dma_chan_dev *dev;

        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 controller driver关心的字段包括:

device,指向该channel所在的dma controller。

cookie,client driver以该channel为操作对象获取传输描述符时,dma controller driver返回给client的最后一个cookie。

completed_cookie,在这个channel上最后一次完成的传输的cookie。dma controller driver可以在传输完成时调用辅助函数dma_cookie_complete设置它的value。

device_node,链表node,用于将该channel添加到dma_device的channel列表中。

router、route_data,TODO。

3.3 struct virt_dma_cha

struct virt_dma_chan用于抽象一个虚拟的dma channel,多个虚拟channel可以共用一个物理channel,并由软件调度多个传输请求,将多个虚拟channel的传输串行地在物理channel上完成。该数据结构的定义如下:

/* drivers/dma/virt-dma.h */ 

struct virt_dma_desc { 
        struct dma_async_tx_descriptor tx; 
        /* protected by vc.lock */ 
        struct list_head node; 
};

struct virt_dma_chan { 
        struct dma_chan chan; 
        struct tasklet_struct task; 
        void (*desc_free)(struct virt_dma_desc *);

        spinlock_t lock;

        /* protected by vc.lock */ 
        struct list_head desc_allocated; 
        struct list_head desc_submitted; 
        struct list_head desc_issued; 
        struct list_head desc_completed;

        struct virt_dma_desc *cyclic;

};

chan,一个struct dma_chan类型的变量,用于和client driver打交道(屏蔽物理channel和虚拟channel的差异)。

task,一个tasklet,用于等待该虚拟channel上传输的完成(由于是虚拟channel,传输完成与否只能由软件判断)。

desc_allocated、desc_submitted、desc_issued、desc_completed,四个链表头,用于保存不同状态的虚拟channel描述符(struct virt_dma_desc,仅仅对struct dma_async_tx_descriptor[2]做了一个简单的封装)。

4. dmaengine向dma controller driver提供的API汇整

damengine直接向dma controller driver提供的API并不多(大部分的逻辑交互都位于struct dma_device结构的回调函数中),主要包括:

1)struct dma_device变量的注册和注销接口

/* include/linux/dmaengine.h */ 
int dma_async_device_register(struct dma_device *device); 
void dma_async_device_unregister(struct dma_device *device);

dma controller driver准备好struct dma_device变量后,可以调用dma_async_device_register将它(controller)注册到kernel中。该接口会对device指针进行一系列的检查,然后对其做进一步的初始化,最后会放在一个名称为dma_device_list的全局链表上,以便后面使用。

dma_async_device_unregister,注销接口。

2)cookie有关的辅助接口,位于“drivers/dma/dmaengine.h”中,包括

static inline void dma_cookie_init(struct dma_chan *chan) 
static inline dma_cookie_t dma_cookie_assign(struct dma_async_tx_descriptor *tx) 
static inline void dma_cookie_complete(struct dma_async_tx_descriptor *tx) 
static inline enum dma_status dma_cookie_status(struct dma_chan *chan, 
        dma_cookie_t cookie, struct dma_tx_state *state)

由于cookie有关的操作,有很多共性,dmaengine就提供了一些通用实现:

void dma_cookie_init,初始化dma channel中的cookie、completed_cookie字段。

dma_cookie_assign,为指针的传输描述(tx)分配一个cookie。

dma_cookie_complete,当某一个传输(tx)完成的时候,可以调用该接口,更新该传输所对应channel的completed_cookie字段。

dma_cookie_status,获取指定channel(chan)上指定cookie的传输状态。

3)依赖处理接口

void dma_run_dependencies(struct dma_async_tx_descriptor *tx);

由前面的描述可知,client可以同时提交多个具有依赖关系的dma传输。因此当某个传输结束的时候,dma controller driver需要检查是否有依赖该传输的传输,如果有,则传输之。这个检查并传输的过程,可以借助该接口进行(dma controller driver只需调用即可,省很多事)。

4)device tree有关的辅助接口

extern struct dma_chan *of_dma_simple_xlate(struct of_phandle_args *dma_spec, 
                struct of_dma *ofdma); 
extern struct dma_chan *of_dma_xlate_by_chan_id(struct of_phandle_args *dma_spec, 
                struct of_dma *ofdma);

上面两个接口可用于将client device node中有关dma的字段解析出来,并获取对应的dma channel。后面实际开发的时候会举例说明。

5)虚拟dma channel有关的API

后面会有专门的文章介绍虚拟dma,这里不再介绍。

5. 编写一个dma controller driver的方法和步骤

上面啰嗦了这么多,相信大家还是似懂非懂(很正常,我也是,dmaengine framework特点就是框架简单,细节复杂)。到底怎么在dmaengine的框架下编写dma controller驱动呢?现在看来,只靠这篇文章,可能达不到目的了,这里先罗列一下基本步骤,后续我们会结合实际的开发过程,进一步的理解和掌握。

编写一个dma controller driver的基本步骤包括(不考虑虚拟channel的情况):

1)定义一个struct dma_device变量,并根据实际的硬件情况,填充其中的关键字段。

2)根据controller支持的channel个数,为每个channel定义一个struct dma_chan变量,进行必要的初始化后,将每个channel都添加到struct dma_device变量的channels链表中。

3)根据硬件特性,实现struct dma_device变量中必要的回调函数(device_alloc_chan_resources/device_free_chan_resources、device_prep_dma_xxx、device_config、device_issue_pending等等)。

4)调用dma_async_device_register将struct dma_device变量注册到kernel中。

5)当client driver申请dma channel时(例如通过device tree中的dma节点获取),dmaengine core会调用dma controller driver的device_alloc_chan_resources函数,controller driver需要在这个接口中奖该channel的资源准备好。

6)当client driver配置某个dma channel时,dmaengine core会调用dma controller driver的device_config函数,controller driver需要在这个函数中将client想配置的内容准备好,以便进行后续的传输。

7)client driver开始一个传输之前,会把传输的信息通过dmaengine_prep_slave_xxx接口交给controller driver,controller driver需要在对应的device_prep_dma_xxx回调中,将这些要传输的内容准备好,并返回给client driver一个传输描述符。

8)然后,client driver会调用dmaengine_submit将该传输提交给controller driver,此时dmaengine会调用controller driver为每个传输描述符所提供的tx_submit回调函数,controller driver需要在这个函数中将描述符挂到该channel对应的传输队列中。

9)client driver开始传输时,会调用dma_async_issue_pending,controller driver需要在对应的回调函数(device_issue_pending)中,依次将队列上所有的传输请求提交给硬件。

10)等等。

6. 参考文档

[1] Linux DMA Engine framework(1)_概述

[2] Linux DMA Engine framework(2)_功能介绍及解接口分析

<think>好的,用户问的是dmaengine_prep_slave_sg和dmaengine_prep_slave_single的区别,需要详细解释。首先,我需要回忆一下Linux内核中DMA引擎的相关API。这两个函数都是用来准备DMA传输请求的,但适用的场景不同。 首先,dmaengine_prep_slave_sg。SG可能代表的是scatter-gather,也就是分散-收集。这种传输方式适用于数据在物理内存中不连续的情况。例如,当应用程序有一个分散在多个缓冲区中的数据块时,使用scatter-gather可以通过一次DMA传输来处理多个分散的缓冲区,而不需要每个缓冲区都单独配置DMA传输。这样能提高效率,减少CPU的干预。 然后是dmaengine_prep_slave_single。这里的single可能指的是单个连续的物理内存区域。也就是说,当要传输的数据块在物理地址上是连续的,可以使用这个函数。它适用于数据已经存放在一个连续的内存块中的情况,比如使用kmalloc分配的内存,或者通过dma_alloc_coherent分配的内存。 接下来需要确认两者的参数区别。dmaengine_prep_slave_sg的参数应该包括一个scatterlist的数组,以及数组的长度,而dmaengine_prep_slave_single可能需要传入一个内存地址和传输长度。这涉及到struct scatterlist的使用,scatterlist用于描述分散的内存块。 另外,还需要考虑DMA传输的方向,比如是从设备到内存还是内存到设备,这两个函数应该都会处理方向参数。不过用户的问题主要是区别,所以重点在于内存是否连续。 可能还需要提到,使用scatter-gather需要硬件DMA控制器的支持。不是所有的DMA控制器都支持scatter-gather传输,因此在使用dmaengine_prep_slave_sg之前,需要确认硬件能力。而dmaengine_prep_slave_single则更基础,可能大多数DMA控制器都支持单次传输。 应用场景的例子可能会有帮助。比如,网络数据包通常由多个碎片缓冲区组成,这时使用scatter-gather传输更高效。而如果处理的是单个大块数据,比如图像缓冲区,可能更适合用single传输。 同时,性能方面,scatter-gather可以减少中断次数和上下文切换,提高吞吐量,尤其是在处理大量小数据块时。而single传输每次只能处理一个连续块,如果有多个不连续块,就需要多次调用,增加开销。 还需要检查函数的具体参数和返回值类型,确保描述的准确性。比如,两者可能都返回一个struct dma_async_tx_descriptor指针,或者可能有错误处理的不同。 最后,总结两者的主要区别在于处理的数据是否物理连续,以及对应的硬件支持情况。确保解释清晰,让用户能根据实际场景选择合适的函数。</think>在Linux内核的DMA引擎框架中,`dmaengine_prep_slave_sg`和`dmaengine_prep_slave_single`是用于准备DMA传输请求的两个关键函数,它们的核心区别在于数据传输场景的适配性。 ### 1. **`dmaengine_prep_slave_single`** - **用途** 适用于**物理内存连续**的单一数据块传输。例如,当数据通过`kmalloc`或`dma_alloc_coherent`分配时,若物理地址连续,适合使用此函数。 - **参数特点** 需要明确指定: - 内存起始地址(物理地址) - 传输长度 - DMA传输方向(如`DMA_MEM_TO_DEV`或`DMA_DEV_TO_MEM`) - **代码示例** ```c struct dma_async_tx_descriptor *tx; tx = dmaengine_prep_slave_single(chan, dma_addr, size, direction, flags); ``` - **适用场景** 传输单个连续内存块,如音频缓冲区、图像帧等。 --- ### 2. **`dmaengine_prep_slave_sg`** - **用途** 处理**物理内存非连续**的分散数据(Scatter-Gather)。通过`scatterlist`描述多个分散的物理内存块,实现单次DMA传输多个缓冲区。 - **参数特点** 需传入: - `scatterlist`数组(描述内存分布) - 数组长度 - DMA传输方向 - **代码示例** ```c struct scatterlist sg[2]; struct dma_async_tx_descriptor *tx; tx = dmaengine_prep_slave_sg(chan, sg, nents, direction, flags); ``` - **硬件依赖** 需要DMA控制器支持Scatter-Gather模式,否则需通过软件模拟(可能降低性能)。 - **适用场景** 网络数据包(多缓冲区组成)、文件I/O的分散读/写等。 --- ### 3. **关键区别总结** | 特性 | `dmaengine_prep_slave_single` | `dmaengine_prep_slave_sg` | |-----------------------|--------------------------------------|--------------------------------------| | **内存连续性** | 必须连续 | 允许非连续(通过`scatterlist`描述) | | **硬件支持** | 基础功能,普遍支持 | 需DMA控制器支持Scatter-Gather | | **性能** | 简单场景高效 | 减少多次传输开销,适合复杂数据流 | | **参数类型** | 单地址+长度 | `scatterlist`数组 | --- ### 4. **选择建议** - 若数据物理连续 → **`dmaengine_prep_slave_single`** - 若数据分散或需批量传输 → **`dmaengine_prep_slave_sg`** - 优先验证硬件支持性(通过`dma_get_slave_caps`检查`SG`能力)。 通过合理选择两者,可优化DMA传输效率并减少CPU干预。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值