linux内核DMA引擎

文章介绍了DMA(直接存储器访问)在执行内存读写操作中的作用,详细阐述了如何进行DMA映射,包括一致性映射和流式映射的使用。同时,讨论了DMA引擎API,包括分配DMA通道、设置从设备参数、获取事务描述符、提交事务以及等待回调通知的过程。此外,还提及了DMADT绑定在设备树中的应用。

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

DMA功能用于执行内存读取和写入操作而不占用CPU周期。当需要传输数据块时,CPU向DMA控制器提供原地址和目的地址以及总字节数,然后DMA控制器会将数据从源传输到目标,而不占用CPU周期。

一、设置DMA映射

        在外设DMA时,根据传输方向指定内存源或目的地址,这里的地址是总线地址,并设置缓存一致性。

        所有的DMA传输都要进行适当的内存映射,DMA映射包括分配DMA缓冲区和为其生成总线地址。DMA设备使用的总线地址,总线地址是dma_addr_t{}类型的实例。

        DMA映射分为两种类型:一致性DMA映射和流式DMA映射。一致性DMA映射可以用于多次DMA传输,它会自动解决缓存一致性的问题。一致性DMA映射通常存在于驱动程序的整个生命周期,而流式映射则通常在DMA传输完成就被取消。  

1. 一致性DMA映射

/* 分配的dma物理内存一定是连续的,其中flags是分配内存的方式GFP_KERNEL或GEP_ATOMIC
*/
void *dma_alloc_coherent(struct device *dev, size_t size,
        dma_addr_t *dma_handle, gfp_t flags);
void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,
        dma_addr_t *dma_handle);

2. 流式DMA映射

        流式DMA映射必须使用已分配的缓冲区;在映射时需要指定传输方向,只能基于该方向使用该DMA映射。

映射分为两种形式:单缓冲映射(只允许单页映射),分散/聚集映射(允许传递多个缓冲区);

单缓冲区映射:

enum dma_data_direction {
    DMA_BIDIRECTIONAL = 0,
    DMA_TO_DEVICE = 1,
    DMA_FROM_DEVICE = 2,
    DMA_NONE = 3,
};

// 其中ptr是内核虚拟地址
dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size, enum
        dma_data_direction direction);

void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
        enum dma_data_direction direction);

// 单页流式映射
dma_addr_t dma_map_page(struct device *dev, struct page *page,
    offset_t offset, size_t size, enum dma_data_direction direction);void dma_unmap_page(struct device *dev, dma_addr_t dma_addr, size_t size,
        enum dma_data_direction direction);

分散/聚集映射

struct scatterlist {
    unsigned long page_link; // 指向page指针struct page *page;
    uint offset;
    uint length;
    dma_addr_t dma_address;
    uint dma_length;
};

int dma_map_sg(struct device *dev, struct scatterlist *sg, uint nents,
        enum dma_data_direction direction);
void dma_unmap_sg(struct device *dev, struct scatterlist *sg, uint nents,
        enum dma_data_direction direction);

/*dmap sg 使用示例*/
u32 wrbuf1 = kzalloc(SDMA_SIZE, GFP_DMA);
u32 wrbuf2 = kzalloc(SDMA_SIZE, GFP_DMA);
u32 wrbuf3 = kzalloc(SDMA_SIZE/2, GFP_DMA);
struct scatterlist sg[3];
sg_init_table(sg, 3);
sg_set_buf(&sg[0], wrbuf1, SDMA_SIZE);
sg_set_buf(&sg[1], wrbuf2, SDMA_SIZE);
sg_set_buf(&sg[2], wrbuf3, SDMA_SIZE/2);
ret = dma_map_sg(NULL, sg, 3, DMA_MEM_TO_MEM);

// 在DMA使用完后临时交给CPU使用,使用完后又返回给CPU
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, uint nents,
        enum dma_data_direction direction);
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, uint nents,
        enum dma_data_direction direction);

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_addr, size_t size,
        enum dma_data_direction direction);
void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_addr,
    size_t size, enum dma_data_direction direction);

二、DMA引擎API

        DMA引擎是开发DMA控制器驱动的通用内核框架。DMA的主要任务是复制内存时减轻CPU的负担,使用通道将事物(IO传输)委托给DMA引擎。DMA引擎则通过DMA控制器驱动程序/API提供一组可供其他设备(DMA从设备)使用的框架。下面是从设备DMA使用DMA引擎的用法。

1. 分配DMA从通道

// 分配释放dma从通道
struct dma_chan *dma_request_channel(const dma_cap_mask *mask,
            dma_filter_fn fn, void *fn_param);
void dma_releas_channel(struct dma_chan *chan);

enum dma_transaction_type {
    DMA_MEMCPY,... , DMA_PRIVATE/*内存不可再memcpy访问*/, 
    DMA_SLAVE/*设备内存之间*/, DMA_CYCLIC, /*设备能够处理循环传输*/
};

// 示例:
dma_cap_mask my_dma_cap_mask;
struct dma_chan *chan;
dma_cap_zero(my_dma_cap_mask);
dmap_cap_set(DMA_MEMCPY, my_dma_cap_mask);
chan = dma_request_channel(my_dma_cap_mask, NULL, NULL);

2. 设置从设备和控制器的特定参数

        DMA从通道的运行时配置struct dma_slave_config{},可以设置DMA方向、DMA地址、总线宽度、DMA突发长度(DMA burst length)

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;
    u32 dst_maxburst;
    [...]
};
//其中dma_slave_buswidth的定义如下:
enum dma_slave_buswidth {
    DMA_SLAVE_BUSWIDTH_UNDEFINED = 0,
    DMA_SLAVE_BUSWIDTH_1_BYTE = 1,
    DMA_SLAVE_BUSWIDTH_2_BYTE = 2,
    DMA_SLAVE_BUSWIDTH_3_BYTE = 3,
    DMA_SLAVE_BUSWIDTH_4_BYTE = 4,
    DMA_SLAVE_BUSWIDTH_8_BYTE = 8,
    DMA_SLAVE_BUSWIDTH_16_BYTE = 16,
    DMA_SLAVE_BUSWIDTH_32_BYTE = 32,
    DMA_SLAVE_BUSWIDTH_64_BYTE = 64,
};
/* 其中src_maxburst和dst_maxburst,一次突发中可以发送到设备的最大字数(这里的字指的是dma_slave_buswidth)。通常是从设备的FIFO大小的一半。*/

// 设置dmaslave 配置
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config);

//示例如下:
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);

3. 获取事务描述符

        在获取的dma_chan{}中都包含一个dma_device{}的指针,而dma_device{}代表一个DMC(DMA控制器),它提供了一系列获取DMA事务的函数:

// 函数的原型为:
struct dma_async_tx_descriptor *(*device_prep_dma)(struct dma_chan *chan,
            dma_addr_t dst, dma_addr_t src, size_t len, ulong flags);

device_prep_dma_memcpy() // 准备memcpy操作
device_prep_dma_sg() // 准备s/g memcpy操作
device_prep_dma_xor() 
device_prep_dma_xor_val()
device_prep_dma_pq()
device_prep_dma_pq_val()
device_prep_dma_memset() // 准备memset操作
device_prep_dma_memset_sg() // 准备memset s/g操作
device_prep_dma_slave_sg() // 准备从设备DMA操作
device_prep_dma_interleaved_dma()

// 这些函数会返回struct dma_async_tx_descriptor{}指针

// 示例:
struct dms_device *dma_dev = my_dma_chan->device;
struct dma_async_tx_descriptor *tx = NULL;
char *dst_data, src_data;
dma_addr_t dma_src, dma_dst;
src_data = kzalloc(BUFFER_SIZE, GFP_DMA);
dst_data = kzalloc(BUFFER_SIZE, GFP_DMA);
dma_src = dma_map_single(NULL, src_data, BUFFER_SIZE, DMA_MEM_TO_MEM);
dma_dst = dma_map_single(NULL, dst_data, BUFFER_SIZE, DMA_MEM_TO_MEM);


tx = dma_dev->device_prep_dma_memcpy(my_dma_chan, dma_dst,
                        dma_src, BUFFER_SIZE, 0);


4. 提交事务

        要将事务放入驱动程序的等待队列中,需要调用dmaengine_submit(),该函数返回一个cookie,可以用来查看DMA活动的进度。注意:dmaengine_submit()不会启动DMA,只是将其放入待处理队列中。

// 提交事物的函数原型
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc);

// 示例:
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)) { printk(KERN_ERR "failed to start dma.");
}

// 对应的callback
static void my_dma_callback() {
    complete(&transfer_ok);
    return;
}

5. 发出挂起的请求并等待回调通知

        在通道上调用dma_async_issue_pending()来激活通道等待队列中的事务。在DMA操作完成时,队列中的下一个事务自动启动,并触发设置的软中断(tasklet),它负责调用用户的回调函数。

// 启动dma操作的函数
void dma_async_issue_pending(struct dma_chan *chan);

// 使用举例:
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);
// 接下来通过tx_data和rx_data处理数据

三、DMA DT绑定

        DMA通道的DT绑定取决于DMA控制器节点(取决于SoC),某些参数(如DMA单元)可能因SoC而异。

下面是绑定示例:

// DT文件中的DMA通道:
uart1 : serial&02020000 {
    compatible = "fsl,imx6sx-uart", "fsl,imx21-uart";
    reg = <0x02020000 0x4000>;
    interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6SX_CLK_UART_IPG>, <&clks IMX6SX_CLK_UART_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;
    dma-names = "rx", "tx";
    status = "disabled";
};

// 对应提取DMA的代码
static int imx_uart_dma_init(struct imx_port *sport) {
    struct dma_slave_config slave_config = {};
    struct device *dev = sport->port.dev;
    int ret;
    
    /*dma rx,注意此处的dma_request_slave_channel()
        会调用of_dma_request_slave_channel()解析DT设备节点,
        收集通道设置。*/
    struct dma_chan_rx  = dma_request_slave_channel(dev, "rx");
    /*...*/
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值