通用设备的动态DMA映射

DMA是数据传输的快速通道,不经过CPU,只占用总线周期。
设置需要用到DMA通道传输数据的源、目的、长度等参数,让DMA自行传输,
传输完毕后,比较源、目的的最终数据,检验是否传输完整并正确。
假设设备驱动程序把一些数据填充到内存缓冲区中,然后立刻命令硬件设备利用DMA传送方式读取该数据。如果DMA访问这些物理RAM内存单元,而相应的硬件高速缓存行的内容还没有写入RAM中,那么硬件设备所读取的至就是内存缓冲区中的旧值。”
现在有两种方法来处理DMA缓冲区:
一致性DMA映射:
书上讲的比较抽象,通俗地所就是任何对DMA缓冲区的改写都会直接更新到内存中,也称之为“同步的”或者“一致的”。
流式DMA映射:
根据个人的理解,这里的流即输入输出流,我们需要事先指定DMA缓冲区的方向,比如是”读缓冲区”还是“写缓冲区”。也称之为“异步的”或“非一致性的”

第一部分 DMA API

为了可以引用DMA API,你必须 #include <linux/dma-mapping.h>

1-1 使用大块DMA一致性缓冲区(dma-coherent buffers)

void * 
dma_alloc_coherent
(struct device *dev, size_t size,
                    dma_addr_t *dma_handle, gfp_t flag)

一致性内存:设备对一块内存进行写操作,处理器可以立即进行读操作,而无需担心处理器高速缓存(cache)的影响。同样的,处理器对一块内存进行些操作,设备可以立即进行读操作。(在告诉设备读内存时,你可能需要确定刷新处理器的写缓存。)

此函数申请一段大小为size字节的一致性内存,返回两个参数。一个是dma_handle,它可以用作这段内存的物理地址。 另一个是指向被分配内存的指针(处理器的虚拟地址)。

注意:由于在某些平台上,使用一致性内存代价很高,比如最小的分配长度为一个页。因此你应该尽可能合并申请一致性内存的请求。最简单的办法是使用dma_pool函数调用(详见下文)。

参数flag(仅存在于dma_alloc_coherent中)运行调用者定义申请内存时的GFP_flags(详见kmalloc)。

void * 
dma_zalloc_coherent
(struct device *dev, size_t size, 
                    dma_addr_t *dma_handle, gfp_t flag)

对dma_alloc_coherent()的封装,如果内存分配成功,则返回清零的内存。

void 
dma_free_coherent
(struct device *dev, size_t size, void *cpu_addr, 
                    dma_addr_t dma_handle)

释放之前申请的一致性内存。dev, size及dma_handle必须和申请一致性内存的函数参数相同。cpu_addr必须为申请一致性内存函数的返回虚拟地址。

注意:和其他内存分配函数不同,这些函数必须要在中断使能的情况下使用。

1-2 使用小块DMA一致性缓冲区

如果要使用这部分DMA API,必须#include <linux/dmapool.h>。

许多驱动程序需要为DMA描述符或者I/O内存申请大量小块DMA一致性内存。你可以使用DMA 内存池,而不是申请以页为单位的内存块或者调用dma_alloc_coherent()。这种机制有点像struct kmem_cache,只是它利用了DMA一致性内存分配器,而不是调用 __get_free_pages()。同样地,DMA 内存池知道通用硬件的对齐限制,比如队列头需要N字节对齐。

struct dma_pool * 
dma_pool_create
(const char *name, struct device *dev, 
                size_t size, size_t align, size_t alloc);

create( )函数为设备初始化DMA一致性内存的内存池。它必须要在可睡眠上下文调用。

name为内存池的名字(就像struct kmem_cache name一样)。dev及size就如dma_alloc_coherent()参数一样。align为设备硬件需要的对齐大小(单位为字节,必须为2的幂次方)。如果设备没有边界限制,可以设置该参数为0。如果设置为4096,则表示从内存池分配的内存不能超过4K字节的边界。

void *
dma_pool_alloc
(struct dma_pool *pool, gfp_t gfp_flags, 
                dma_addr_t *dma_handle);

从内存池中分配内存。返回的内存同时满足申请的大小及对齐要求。设置GFP_ATOMIC可以确保内存分配被block,设置GFP_KERNEL(不能再中断上下文,不会保持SMP锁)允许内存分配被block。和dma_alloc_coherent()一样,这个函数会返回两个值:一个值是cpu可以使用的虚拟地址,另一个值是内存池设备可以使用的dma物理地址。

void 
dma_pool_free
(struct dma_pool *pool, void *vaddr, 
                dma_addr_t addr);

返回内存给内存池。参数pool为传递给dma_pool_alloc()的pool,参数vaddr及addr为dma_pool_alloc()的返回值。

void 
dma_pool_destroy
(struct dma_pool *pool);

内存池析构函数用于释放内存池的资源。这个函数在可睡眠上下文调用。请确认在调用此函数时,所有从该内存池申请的内存必须都要归还给内存池。

1-3 DMA寻址限制

int 
dma_supported
(struct device *dev, u64 mask)

用来检测该设备是否支持掩码所表示的DMA寻址能力。比如mask为0x0FFFFFF,则检测该设备是否支持24位寻址。

返回1表示支持,0表示不支持。

注意:该函数很少用于检测是否掩码为可用的,它不会改变当前掩码设置。它是一个内部API而非供驱动者使用的外部API。

int 
dma_set_mask
(struct device *dev, u64 mask)

检测该掩码是否合法,如果合法,则更新设备参数。即更新设备的寻址能力。

返回0表示成功,返回负值表示失败。

int 
dma_set_coherent_mask
(struct device *dev, u64 mask)

检测该掩码是否合法,如果合法,则更新设备参数。即更新设备的寻址能力。

返回0表示成功,返回负值表示失败。

u64 
dma_get_required_mask
(struct device *dev)

该函数返回平台可以高效工作的掩码。通常这意味着返回掩码是可以寻址到所有内存的最小值。检查该值可以让DMA描述符的大小尽量的小。

请求平台需要的掩码并不会改变当前掩码。如果你想利用这点,可以利用改返回值通过dma_set_mask()设置当前掩码。

1-4 流式DMA映射

dma_addr_t 
dma_map_single
(struct device *dev, void *cpu_addr, size_t size,
                enum dma_data_direction direction)

映射一块处理器的虚拟地址,这样可以让外设访问。该函数返回内存的物理地址。

在dma_API中强烈建议使用表示DMA传输方向的枚举类型。

DMA_NONE    仅用于调试目的
DMA_TO_DEVICE    数据从内存传输到设备,可认为是写操作。
DMA_FROM_DEVICE    数据从设备传输到内存,可认为是读操作。
DMA_BIDIRECTIONAL    不清楚传输方向则可用该类型。

请注意:并非一台机器上所有的内存区域都可以用这个API映射。进一步说,对于内核连续虚拟地址空间所对应的物理地址并不一定连续(比如这段地址空间由vmalloc申请)。因为这种函数并未提供任何分散/聚集能力,因此用户在企图映射一块非物理连续的内存时,会返回失败。基于此原因,如果想使用该函数,则必须确保缓冲区的物理内存连续(比如使用kmalloc)。

更进一步,所申请内存的物理地址必须要在设备的dma_mask寻址范围内(dma_mask表示与设备寻址能力对应的位)。为了确保由kmalloc申请的内存在dma_mask中,驱动程序需要定义板级相关的标志位来限制分配的物理内存范围(比如在x86上,GFP_DMA用于保证申请的内存在可用物理内存的前16Mb空间,可以由ISA设备使用)。

同时还需注意,如果平台有IOMMU(设备拥有MMU单元,可以进行I/O内存总线和设备的映射,即总线地址和内存物理地址的映射),则上述物理地址连续性及外设寻址能力的限制就不存在了。当然为了方便起见,设备驱动开发者可以假设不存在IOMMU。

警告:内存一致性操作基于高速缓存行(cache line)的宽度。为了可以正确操作该API创建的内存映射,该映射区域的起始地址和结束地址都必须是高速缓存行的边界(防止在一个高速缓存行中有两个或多个独立的映射区域)。因为在编译时无法知道高速缓存行的大小,所以该API无法确保该需求。因此建议那些对高速缓存行的大小不特别关注的驱动开发者们,在映射虚拟内存时保证起始地址和结束地址都是页对齐的(页对齐会保证高速缓存行边界对齐的)。

DMA_TO_DEVICE    软件对内存区域做最后一次修改后,且在传输给设备前,需要做一次同步。一旦该使用该原语,内存区域可被视作设备只读缓冲区。如果设备需要对该内存区域进行写操作,则应该使用DMA_BIDIRECTIONAL(如下所示)

DMA_FROM_DEVICE    驱动在访问数据前必须做一次同步,因为数据可能被设备修改了。内存缓冲区应该被当做驱动只读缓冲区。如果驱动需要进行写操作,应该使用DMA_BIDIRECTIONAL(如下所示)。

DMA_BIDIRECTIONAL    需要特别处理:这意味着驱动并不确定内存数据传输到设备前,内存是否被修改了,同时也不确定设备是否会修改内存。因此,你必须需要两次同步双向内存:一次在内存数据传输到设备前(确保所有缓冲区数据改变都从处理器的高速缓存刷新到内存中),另一次是在设备可能访问该缓冲区数据前(确保所有处理器的高速缓存行都得到了更新,设备可能改变了缓冲区数据)。即在处理器写操作完成时,需要做一次刷高速缓存的操作,以确保数据都同步到了内存缓冲区中。在处理器读操作前,需要更新高速缓冲区的行,已确保设备对内存缓冲区的改变都同步到了高速缓冲区中。

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

取消先前的内存映射。传入该函数的所有参数必须和映射API函数的传入(包括返回)参数相同。

dma_addr_t 
dma_map_page(struct device *dev, struct page *page,
                    unsigned long offset, size_t size,
                    enum dma_data_direction direction)

void 
dma_unmap_page
(struct device *dev, dma_addr_t dma_address, size_t size,
                enum dma_data_direction direction)

对页进行映射/取消映射的API。对其他映射API的注意事项及警告对此都使用。同样的,参数<offset>及<size>用于部分页映射,如果你对高速缓存行的宽度不清楚的话,建议你不要使用这些参数。

int 
dma_mapping_error
(struct device *dev, dma_addr_t dma_addr)

在某些场景下,通过dma_map_single及dma_map_page创建映射可能会失败。驱动程序可以通过此函数来检测这些错误。一个非零返回值表示未成功创建映射,驱动程序需要采取适当措施(比如降低当前DMA映射使用率或者等待一段时间再尝试)。

int
dma_map_sg(struct device *dev, struct scatterlist *sg,
        int nents, enum dma_data_direction direction)

返回值:被映射的物理内存块的数量(如果在分散/聚集链表中一些元素是物理地址或虚拟地址相邻的,切IOMMU可以将它们映射成单个内存块,则返回值可能比输入值<nents>小)。

请注意如果sg已经映射过了,其不能再次被映射。再次映射会销毁sg中的信息。

如果返回0,则表示dma_map_sg映射失败,驱动程序需要采取适当措施。驱动程序在此时做一些事情显得格外重要,一个阻塞驱动中断请求或者oopsing都总比什么都不做导致文件系统瘫痪强很多。

下面是个分散/聚集映射的例子,假设scatterlists已经存在。

int i, count = dma_map_sg(dev, sglist, nents, direction);
struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) {
        hw_address[i] = sg_dma_address(sg);
        hw_len[i] = sg_dma_len(sg); 
}

其中nents为sglist条目的个数。

这种实现可以很方便将几个连续的sglist条目合并成一个(比如在IOMMU系统中,或者一些页正好是物理连续的)。

然后你就可以循环多次(可能小于nents次)使用sg_dma_address() 及sg_dma_len()来获取sg的物理地址及长度。

void 
dma_unmap_sg
(struct device *dev, struct scatterlist *sg,
        int nhwentries, enum dma_data_direction direction)

取消先前分散/聚集链表的映射。所有参数和分散/聚集映射API的参数相同。

注意:<nents>是传入的参数,不一定是实际返回条目的数值。

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle, size_t size,
                                enum dma_data_direction direction)

void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle, size_t size,
                                enum dma_data_direction direction)

void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems,
                            enum dma_data_direction direction)

void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, int nelems,
                            enum dma_data_direction direction)

为CPU及外设同步single contiguous或分散/聚集映射。

注意:你必须要做这个工作,

  • 在CPU读操作前,此时缓冲区由设备通过DMA写入数据(DMA_FROM_DEVICE)

  • 在CPU写操作后,缓冲区数据将通过DMA传输到设备(DMA_TO_DEVICE)

  • 在传输数据到设备前后(DMA_BIDIRECTIONAL)

dma_addr_t 
dma_map_single_attrs
(struct device *dev, void *cpu_addr, size_t size,
                    enum dma_data_direction dir,
                    struct dma_attrs *attrs)

void 
dma_unmap_single_attrs
(struct device *dev, dma_addr_t dma_addr, 
                    size_t size, enum dma_data_direction dir,
                    struct dma_attrs *attrs)

int 
dma_map_sg_attrs
(struct device *dev, struct scatterlist *sgl, 
                int nents, enum dma_data_direction dir,
                struct dma_attrs *attrs)

void 
dma_unmap_sg_attrs
(struct device *dev, struct scatterlist *sgl, 
                    int nents, enum dma_data_direction dir,
                    struct dma_attrs *attrs)

这四个函数除了传入可选的struct dma_attrs*之外,其他和不带_attrs后缀的函数一样。

struct dma_attrs概述了一组DMA属性。struct dma_attrs详细定义请参见linux/dma-attrs.h。

DMA属性的定义是和体系结构相关的,并且Documentation/DMA-attributes.txt有详细描述。

如果struct dma_attrs* 为空,则这些函数可以认为和不带_attrs后缀的函数相同。

下面给出一个如何使用*_attrs 函数的例子,当进行DMA内存映射时,如何传入一个名为DMA_ATTR_FOO的属性:

#include <linux/dma-attrs.h> 
/* DMA_ATTR_FOO should be defined in linux/dma-attrs.h and
* documented in Documentation/DMA-attributes.txt */ 
...
        DEFINE_DMA_ATTRS(attrs);
        dma_set_attr(DMA_ATTR_FOO, &attrs);
        ....
        n = dma_map_sg_attrs(dev, sg, nents, DMA_TO_DEVICE, &attr);
        ....

在映射/取消映射的函数中,可以检查DMA_ATTR_FOO是否存在:

void whizco_dma_map_sg_attrs(struct device *dev, dma_addr_t dma_addr,
                            size_t size, enum dma_data_direction dir,
                            struct dma_attrs *attrs) 
{
        ....
        int foo = dma_get_attr(DMA_ATTR_FOO, attrs);
        ....
        if (foo)
            /* twizzle the frobnozzle */
        ....

Linux下DMA驱动相关推荐

  1. linux dma驱动,linux下DMA驱动测试代码

    DMA传输可以是内存到内存.内存到外设和外设到内存.这里的代码通过dma驱动实现了内存到内存的数据传输. /* Function description:When we call dmatest_re ...

  2. Linux下DMA驱动api 以及测试实列

    dmaengine framwork主要分为两部分:DMA controller 和DMA engine API.涉及内核相关文档:Documentation/damengine目录.Document ...

  3. linux 网络dma驱动,S3C2410的Linux下DMA驱动程序开发

    网上介绍Linux下的一般驱动程序开发示例浩如烟海,或是因为简单,关于DMA驱动的介绍却寥寥无几:近期因工作需要,花了几日时间开发了某设备在S3C2410处理器Linux下DMA通信的驱动程序,有感于 ...

  4. 【驱动】linux下I2C驱动架构全面分析

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

  5. linux下I2C驱动发送IO时序,I2C驱动情景分析——怎样控制I2C时序

    内核版本:linux-3.4.2 源程序:    linux-3.4.2\drivers\i2c\busses\I2c-s3c2410.c 这次要解决的问题是:如何配置soc的I2C模块,输出想要的时 ...

  6. linux下I2C驱动架构全面分析

    I2C 概述 I2C是philips提出的外设总线. I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线. ...

  7. linux下IIC驱动开发分析

    1.  IIC规范 IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备.IIC总线产生于在80年代,最初为音频和 ...

  8. nvidia Quadro P620在linux下安装驱动

    nvidia Quadro P620在linux下安装驱动 在官网上下载最新的驱动: NVIDIA-Linux-x86_64-460.67.run 执行安装的时候提示: ERROR: You appe ...

  9. linux系统下的打印机驱动下载,方法论:Linux下如何驱动主流品牌打印机

    薄荷站长浸淫 Linux 桌面领域十余年,一直致力于 Linux 桌面系统的推广.对于桌面用户办公中必需的打印功能,薄荷站长也是有所研究.为了使新手朋友们少走弯路,薄荷站长介绍一下常见品牌打印机的 L ...

最新文章

  1. 菜鸟学Linux 第044篇笔记 算法和私有CA
  2. 样式文件修改后不起作用_Word样式,这个功能好用到让你忘不了!
  3. python怎么把程序封装成函数_PYTHON中如何把固定格式代码,封装成一个函数?
  4. python获取天气信息写入原有的excel文档
  5. 华章7-8月份新书简介(2015年)
  6. mvc4 html.pager,MVC分页之MvcPager使用详解
  7. 【模式识别】K均值聚类算法应用实验报告及MATLAB仿真
  8. 看看大货车到底有多少盲区,肯定用得到!救命的!
  9. 高并发来袭,面向Google编程的程序员要小心了!
  10. redis 重新加载_Redis持久化和Redis持久化方式
  11. robocopy 备份_备份双雄!Robocopy和XXCOPY使用详解
  12. 【Rainmeter】简简单单的 一言 小皮肤
  13. 最适合晚上睡不着看的 7 个网站,建议收藏哦
  14. 尘梦回还服务器在维护中是什么意思,20190925维护公告解读
  15. 了解阿克曼转向原理的作用
  16. Python学习,第七课(灵活使用Frame,让布局更舒适)
  17. Virtual COM port 该设备的驱动程序未被安装:INF中服务安装段落无效错误的处理办法
  18. SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside stri
  19. 代码规范Sonar报Raw types should not be used
  20. 计算机考研408每日一题 day121

热门文章

  1. 业务流程管理(BPM)系统的九大必备特点
  2. linux下低格u盘,u盘低级格式化操作
  3. 关于JSON存放List集合的顺序问题
  4. 【STM32CubeMx你不知道的那些事】第十章:STM32CubeMx的SPI外置FLASH(W25Q128)+文件系统(FATFS)+虚拟U盘
  5. OSChina 周日乱弹 ——我对象整天在家打游戏,怎么办?
  6. sum if函数的精妙及高级用法:
  7. 科研萌新成长记17——落地
  8. NAACL 2019 | ​注意力模仿:通过关注上下文来更好地嵌入单词
  9. 从前慢-Shiro和JWT
  10. GTA5 无法登陆,无法创建登陆令牌问题解决