首页 > 其他分享 >DMA映射

DMA映射

时间:2024-03-24 18:12:50浏览次数:37  
标签:DMA 映射 dma device 内存 缓冲区

参考资料: https://elinux.org/images/3/32/Pinchart--mastering_the_dma_and_iommu_apis.pdf 《宋宝华:Linux设备驱动开发详解》  

VA和PA的相互转换:

系统启动时,MMU便会建立映射表,将DRAM物理地址和虚拟地址进行映射,在linux内核中,可以使用下面的函数完成VA和PA的转换
#define virt_to_bus(virt)    (virt_to_phys(virt))

void *phys_to_virt(phys_addr_t address);
phys_addr_t virt_to_phys(const volatile void *address);

// 参数说明
address:要转换的虚拟地址

#define phys_to_virt(vaddr) ((void *)((unsigned long)(vaddr)+PAGE_OFFSET))
但从代码发现,phys_to_virt和virt_to_phys都获取到的内核地址仅做了简单的偏移,例如0x817000000经过phys_to_virt获取到的内核地址为0xffff000817000000  

DMA地址掩码:

设备不一定能够在所有的内存地址上进行DMA操作,在这这种情况下应该通过下列函数执行DMA地址掩码:
int dma_set_mask(strcut device *dev, u64 mask);
例如,对于只能在32位地址上执行DMA操作的设备而言,就应该调用dma_set_mask(dev, 0xffffffff)。 其实该API的本质就是修改了device结构体中的dma_mask成员,如下:
int arm_dma_set_mask(struct device* dev, u64 dma_mask)
{
    if(!dev->dma_mask || !dma_supported(dev, dma_mask))
        return -EIO;
    *dev->dma_mask = dma_mask;
    
    return 0;
}
在device结构体中,除了有dma_mask之外,还有一个coherent_dma_mask成员,dma_mask是设备DMA可以寻址的范围,而coherent_dma_mask作用用于申请一致性的DMA缓冲区:  

一致性DMA缓冲区:

DMA映射包括两个部分的工作:分配一片DMA缓冲区,为这片缓冲区产生设备可访问的地址。同时,DMA映射必须也要考虑cache一致性的问题。内核提供如下函数以分配DMA一致性的内存区域:
void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp); 

//分配DMA缓存区
//返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,需要释放,避免内存泄漏
//参数如下:
  //*dev:指针,这里填0,表示这个申请的缓冲区里没有内容
  //size:分配的地址大小(字节单位)
  //*handle:申请到的物理起始地址
  //gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
        //GFP_ATOMIC    用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
        //GFP_KERNEL    内核内存的正常分配. 可能睡眠.
      //GFP_USER      用来为用户空间页来分配内存; 它可能睡眠. 
dma_alloc_coherent函数返回值表示kernel看到的地址,返回值表示dma设备看到的地址   对应的释放函数如下:
dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)    //释放DMA缓存,与dma_alloc_coherent ()对应

//size:释放长度
//cpu_addr:虚拟地址,
//handle:物理地址
两者的区别:
  1. dma_alloc_writecombine
  • dma_alloc_writecombine 函数用于分配一块内存区域,该内存区域可以通过 DMA 进行写操作(如数据传输到设备)。
  • 分配的内存区域通常是通过写结合(write-combining)机制来实现的,这种机制可以提高性能,减少内存复制和刷新的开销。
  • 适用于一些需要高性能写操作的场景,比如网络设备的数据传输。
  1. dma_alloc_coherent
  • dma_alloc_coherent 函数也用于分配一块内存区域,但是它分配的内存区域是直接连续的物理内存,适合 DMA 读写操作。
  • 内核会保证返回的内存区域是物理上连续的,以便于 DMA 控制器直接访问这块内存进行数据传输。
  • 适用于需要进行 DMA 读写操作的场景,如一些设备对内存访问要求严格的情况。
  dma_alloc_writecombine对应的释放函数:dma_free_writecombine dma_free_writecombine函数定义如下:
void *dma_free_writecombine(struct device *dev, size_t size, cpu_addr, handle)        \
    dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle)
需要注意的一点是,dma_alloc_xxx()函数虽然是dma_alloc_开头的,但是其申请的区域不一定在DMA区域中。以32位ARM处理器为例,当coherent_dma_mask小于0xffffffff时,才会设置GPA_DMA标记,并从DMA区域去申请内存。 当使用ARM时,像GPU、 isp等设备需要预留大量连续的内存,这部分内存平时不用,但是一般的做法又必须先预留。CMA机制,可以做到不预留内存,这些内存是平时可用的,只是当有需要的时候才会被分配给isp设备。  

流式DMA映射:

需要流式映射的原因:

并不是所有的DMA缓冲区都是驱动申请的,如果是驱动申请的,用一致性DMA映射自然最方便,一致性直接考虑了cache一致性问题了。但是,缓冲区来自内核的较上层(例如网络报文,块设备要写入设备的数据),上层很可能使用的是kmalloc()等方法去申请的,这时就需要用到流式DMA映射。  

流式映射相对于一致性映射有更多的限制点,以下原因不同于一致性映射:

1、映射需要使用已经分配的缓冲区 2、映射可以接收几个分散的不连续缓冲区 3、映射的缓冲区属于设备而不属于cpu。cpu使用缓冲区之前,应该首先解除映射(在dma_unmap_single()或者dma_unmap_sg()之后)。这是为了缓存 4、对于写入事务(CPU到设备),驱动程序应该在映射之前将数据放入缓冲区 5、必须指定数据移动的方向,只能基于该方向使用数据 为啥在取消映射之前不能访问缓冲区呢?原因很简单:CPU映射是可缓存的。用于流式映射的dma_map_xxx()系列函数将首先清理与缓冲区相关的缓存使之无效,在出现相应的dma_unmap_xxx()之前,CPU不能访问。   流式DMA映射本质上大多就是进行cache的使无效或清除操作  

流式映射有两种形式:

1、单缓冲区映射,只允许单页映射 2、分散/聚集映射,允许传递多个缓冲区(分散在内存中) 对于这两种形式,都必须指定方向:
enum dam_data_direction {
    DMA_BIDIRECTIONAL = 0,
    DMA_TO_DEVICE = 1;
    DMA_FROM_DEVICE = 2;
    DMA_NONE = 3;
};
 

单缓冲区映射:

适用于偶然使用的映射,可用dma_map_single实现流式DMA映射,dma_unmap_single取消流式映射:
dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size, enum dma_data_direction direction);
// 参数和返回值说明
dev:指向代表要进行 DMA 操作的设备的 struct device 结构体的指针。
ptr:指向要映射的内存区域的起始虚拟地址。
size:要映射的内存区域的大小。
direction:数据传输方向,可以是 DMA_TO_DEVICE(表示数据从内存传输到设备)、DMA_FROM_DEVICE(表示数据从设备传输到内存)或者 DMA_BIDIRECTIONAL(表示双向数据传输)。
dma_addr_t:表示映射后的物理地址(即 DMA 地址)。如果映射成功,则返回映射后的物理地址;如果映射失败,则返回一个特定的错误码。

void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction);
// 参数说明
dev:指向代表进行 DMA 操作的设备的 struct device 结构体的指针。
dma_addr:要取消映射的 DMA 地址,即之前调用 dma_map_single 返回的物理地址。
size:取消映射的内存区域的大小,应与之前映射时传入的大小相同。
direction:数据传输方向,应与之前映射时传入的方向参数相同。
通常情况下,设备驱动不应该访问unmap的流式映射DMA缓冲区,如果一定要这么做,可以先使用下列函数获得DMA缓冲区的拥有权:
void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction);

// 参数说明
dev:指向进行 DMA 操作的设备的 struct device 结构体的指针。
dma_handle:要同步的 DMA 地址,即之前通过 dma_map_single 函数获得的 DMA 地址。
size:要同步的内存区域的大小,应与之前映射时传入的大小相同。
direction:数据传输方向,通常应与之前映射时传入的方向参数相同,用于指示数据流向。
驱动访问完DMA缓冲区后,应该将其所有权返还给设备,如下:
dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size, enum dma_data_direction direction);

// 参数说明
dev:指向进行 DMA 操作的设备的 struct device 结构体的指针。
dma_handle:要同步的 DMA 地址,即之前通过 dma_map_single 函数获得的 DMA 地址。
size:要同步的内存区域的大小,应与之前映射时传入的大小相同。
direction:数据传输方向,通常应与之前映射时传入的方向参数相同,用于指示数据流向。
 

分散/聚集映射:

分散/聚集映射是一种特殊类型的流式DMA映射,可以在单个槽中传输多个缓冲区区域,而不是单独映射每个缓冲区并逐个传输。假设有几个缓冲区物理上可能不是连续的,所有这些缓冲区都需要同时传输到设备或者从设备传输,这些情况可能出现的原因: 1、readv或writev系统调用 2、磁盘I/O请求   内核将分散列表表示为:
struct scatterlist {
    struct scatterlist *next;   // 指向下一个 scatterlist 结构体的指针
    unsigned int        offset;    // 缓冲区在页中的偏移
    unsigned long       dma_address;  // DMA 地址
    unsigned int        dma_length;   // 数据长度
    unsigned int        dma_offset;   // 数据偏移量
};

 

为了设置分散列表映射,应该进行如下操作: 1、分配分散的缓冲区 2、创建分散列表数组,并使用sg_set_buf()分配的内存填充它。注:分散页表必须是页面大小(除结尾外) 3、在该分散列表上调用dma_map_sg();负责缓存一致性 4、一旦完成DMA映射,就调用dma_unmap_sg()来取消映射分散列表   例如:
u32 *wbuf, *wbuf2, *wbuf3;
wbuf = kzmalloc(SDMA_BUF_SIZE, GFP_DMA);
wbuf2 = kzmalloc(SDMA_BUF_SIZE, GFP_DMA);
wbuf3 = kzmalloc(SDMA_BUF_SIZE/2, GFP_DMA);

struct scatterlist sg[3];
sg_init_table(sg, 3);
sg_set_buf(&sg[0], wbuf, SDMA_BUF_SIZE);
sg_set_buf(&sg[1], wbuf2, SDMA_BUF_SIZE);
sg_set_buf(&sg[2], wbuf3, SDMA_BUF_SIZE/2);
ret = dma_map_sg(NULL, sg, 3, DMA_MEM_TO_MEM);

 

    单缓冲区映射使用demo:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>

#define BUF_SIZE 4096

static struct device *dummy_device;
static dma_addr_t dma_handle;
static char *dma_buffer;

static int __init dma_sync_demo_init(void)
{
    // 分配内存作为 DMA 缓冲区
    dma_buffer = kmalloc(BUF_SIZE, GFP_KERNEL);
    if (!dma_buffer) {
        pr_err("Failed to allocate DMA buffer\n");
        return -ENOMEM;
    }

    // 映射 DMA 缓冲区
    dma_handle = dma_map_single(dummy_device, dma_buffer, BUF_SIZE, DMA_BIDIRECTIONAL);
    if (dma_mapping_error(dummy_device, dma_handle)) {
        pr_err("Failed to map DMA buffer\n");
        kfree(dma_buffer);
        return -ENOMEM;
    }

    // 修改 DMA 缓冲区数据
    memset(dma_buffer, 0xAA, BUF_SIZE);

    // 同步 DMA 内存到 CPU 缓存
    dma_sync_single_for_cpu(dummy_device, dma_handle, BUF_SIZE, DMA_BIDIRECTIONAL);

    // 在这里可以处理同步后的数据

    return 0;
}

static void __exit dma_sync_demo_exit(void)
{
    // 取消映射 DMA 缓冲区
    dma_unmap_single(dummy_device, dma_handle, BUF_SIZE, DMA_BIDIRECTIONAL);

    // 释放 DMA 缓冲区内存
    kfree(dma_buffer);
}

module_init(dma_sync_demo_init);
module_exit(dma_sync_demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DMA Sync Demo");

标签:DMA,映射,dma,device,内存,缓冲区
From: https://www.cnblogs.com/lethe1203/p/18092749

相关文章

  • DMA cache一致性
    本节内容参考《宋宝华:Linux设备驱动开发详解》 cache和DMA本身似乎是两个毫无关联的事物。cache被用作CPU针对内存的缓存,利用程序的空间局部性和时间局部性原理,达到较高的命中率,从而避免CPU每次都必须要与相对慢速的内存交互数据来提高数据的访问速率。DMA可以作为内存与外设之......
  • dw_axi_dmac简介
    参考资料:https://blog.csdn.net/as480133937/article/details/104927922【ARMAMBAAXI入门2-AXI协议中的BURST】AXI3/4协议_axi3协议-CSDN博客【注】:关于dw_axi_dmac的理解是我个人理解,无法保证理解的正确性 基本概念:DMA:全称directmemoryaccess,即直接存储......
  • c++ stl 之映射—— map 详解
     map是stl的一个关联容器,名叫“映射”,何为“映射”?其实就是一个数组,但有了数组何必还需映射,这是一个高深的问题。目录一、map简介         1.空间复杂度    2.时间复杂度     3.“键”的类型二、 map用法     1.声明  ......
  • CF1615F LEGOndary Grandmaster
    CF1615FLEGOndaryGrandmaster计数好题,转换条件+转化贡献+组合数首先题目的操作没有什么好的性质,考虑一个经典的trick,将奇数位置上的数字取反,于是题目的操作变成\(01\rightarrow10\)或\(10\rightarrow01\)。这个操作的性质就是序列中\(1\)的总数不变,并且操作可以抽象......
  • Podman能够替代Docker吗
    导读:参考:ExploringPodman:AMoreSecureDockerAlternative作者:MarinBezhanov网址:https://betterstack.com/community/guides/scaling-docker/podman-vs-docker/该随笔为文章部分摘要和学习笔记架构区别Docker属于CS架构(client-server),Podman利用了无守护架构(daemonless......
  • 地址映射
    MMU的相关概念可参考:https://www.cnblogs.com/lethe1203/p/18064515Linux驱动开发在某些情况也会直接操作寄存器 MMU主要完成的功能:1、完成虚拟空间到物理空间的映射2、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性对于32位的处理器来说,虚拟地址范围为:2^32=4......
  • springMVC映射请求数据
    获取参数值@RequestParam@RequestParam表示会接收参数,value="name"表示接收的参数名为name,,required=false表示该参数可以没有,默认为truepackagecom.ysbt.requestparam;importorg.springframework.stereotype.Controller;importorg.springframework.web.bind.annotation.......
  • elFinder的目录映射
     目录映射一级文件夹创建符号连接;选择源链接点在elFinder相应文件夹的空白处右击,选择「创建为」→「符号连接」修改php\\connector.minimal.php文件,新增一个项目卷。大约是在165行之后。修改'path’一行中的路径修改'URL'一行中的路径(与path中的保持一致)//......
  • MyBatis的关联映射
    资料下载链接MyBatis的关联映射-1V1MyBatis的关联映射-1VnMyBatis的关联映射-nVn一、需求分析        掌握一对一关联映射,掌握一对多关联映射,掌握多对多关联映射。二、搭建环境1)数据库环境        mybatis数据库,运行mybatistest04.sql2)引......
  • 2、URL和视图的映射
    fromflaskimportFlask,requestapp=Flask(__name__)#url的组成部分:http[80]/https[443]://www.qq.com:443/path#默认的http协议使用的是80端口,https协议使用的是443端口.#当我们输入www.qq.com时,实际浏览器会处理加上443端口#url与视图:path与视图@app.route("/")d......