DMA映射 dma_addr_t-程序员宅基地

DMA映射

一个DMA映射是要分配的DMA缓冲区与为该缓冲区生成的、设备可访问地址的组合。

DMA映射建立了一个新的结构类型——dma_addr_t来表示总线地址。dma_addr_t类型的变量对驱动程序是不透明的, 唯一允许的操作是将它们传递给DMA支持例程以及设备本身。

根据DMA缓冲区期望保留的时间长短,PCI代码有两种DMA映射: 1) 一致性映射 ; 2) 流式DMA映射(推荐)。

建立一致性DMA映射

void *dma_alloc_coherent(struct device *dev,size_t size, dma_addr_t *dma_handle,int flag); 该函数处理了缓冲区的分配和映射。

前两个参数是device结构和所需缓冲区的大小。 函数在两处返回结果: 1)函数的返回值时缓冲区的内核虚拟地址,可以被驱动程序使用。 2)相关的总线地址则保存在dma_handle中。通常只能通过get_free_pages函数分配内存(size是以字节为单位的)。flag参数通常是描述如何分配内存的GFP_值;通常是GFP_KERNEL或者是GFP_ATOMIC(在原子上下文中运行时)。

向系统返回缓冲区: void dma_free_coherent(struct device *dev,size_t size, void *vaddr,dma_addr_t dma_handle);

DMA池

DMA池是一个生成小型、一致性DMA映射的机制。 调用dma_alloc_coherent函数获得的映射,可能其最小大小为单个页。 如果设备需要的DMA区域比这还小,就要用DMA池了。

在<linux/dmapool.h>中定义了DMA池的函数。DMA池必须在使用前,调用下面的函数创建:

struct dma_pool *dma_pool_create(const char *name,struct device *dev, size_t size,size_t align, size_t allocation); name是DMA池的名字,dev是device结构,size是从该池中分配的缓冲区大小, align是该池分配操作所必须遵守的硬件对齐原则(用字节表示),如果allocation不为零,表示内存边界不能超越allocation。

释放DMA池: void dma_pool_destroy(struct dma_pool *pool);

在销毁前,必须向DMA池返回所有分配的内存。

DMA池分配内存 void *dma_pool_alloc(struct dma_pool *pool,int mem_flags, dma_addr_t *handle);

在这个函数中,mem_flags通常设置为GFP_分配标志。像dma_alloc_coherent函数一样,返回的DMA缓冲区的地址是内核虚拟地址,并作为总线地址保存在handle中。

释放内存 void dma_pool_free(struct dma_pool *pool,void *vaddr,dma_addr_t addr);

建立流式DMA映射

流式映射具有比一致性映射更为复杂的接口。 这些映射希望能与已经由驱动程序分配的缓冲区协同工作, 因而不得不处理那些不是它们选择的地址。

当建立流式映射时,必须告诉内核数据流动的方向。 枚举类型dma_data_direction:

  DMA_TO_DEVICE  数据发送到设备(如write系统调用)

  DMA_FROM_DEVICE  数据被发送到

  CPU DMA_BIDIRECTIONAL 数据可双向移动

  DMA_NONE  出于调试目的。

当只有一个缓冲区要被传输的时候,使用dma_map_single函数映射它:

dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size, enum dma_data_direction direction); 返回值是总线地址,可以把它传递给设备。

当传输完毕后,使用dma_unmap_single函数删除映射:

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

流式DMA映射的几条原则:

  • 缓冲区只能用于这样的传送,即其传送方向匹配于映射时给定的方向。
  • 一旦缓冲区被映射,它将属于设备,而不是处理器。 直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。
  • 在DMA处于活动期间内,不能撤销对缓冲区映射,否则会严重破坏系统的稳定性。

驱动程序需要不经过撤销映射就访问流式DMA缓冲区的内容,有如下调用:

void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr, size_t size,enum dma_data_direction direction);

在设备访问缓冲区前,应调用下面函数将缓冲区所有权交还给设备:

void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr, size_t size,enum dma_data_direction direction);

单页流式映射

有时候,要为page结构指针指向的缓冲区建立映射,比如 为get_user_pages获得的用户空间缓冲区。使用下面的函数,建立和撤销使用page结构指针的流式映射:

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);

分散/聚集映射

分散/聚集映射是一种特殊类型的流式DMA映射。假设有几个缓冲区,它们需要与设备双向传输数据。可以简单地依次映射每一个缓冲区并且执行请求的操作, 但是一次映射整个缓冲区表还是很有利的。

映射分散表的第一步是建立并填充一个描述被传输缓冲区的 scatterlist结构的数组。 <linux/scatterlist.h>

scatterlist结构的成员:struct page *page;

           unsigned int length;

           unsigned int offset;

然后调用:

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

这里nents是传入的分散表入口的数量。返回值是要传送的DMA缓冲区数,它可能小于nents。

驱动程序应该传输有dma_map_sg函数返回的每个缓冲区。总线地址和每个缓冲区的长度被保存在scatterlist结构中,但是它们在结构中的位置会随体系结构的不同而不同。使用已经定义的两个宏,以用来编写可移植代码:

dma_addr_t sg_dma_address(struct scatterlist *sg); 从该分散表的入口项中返回总线(DMA)地址。

unsigned int sg_dma_len(struct scatterlist *sg); 返回缓冲区的长度。

一旦传输完毕,解除分散/聚集映射:

void dma_unmap_sg(struct device *dev, struct scatterlsit *list, int nents, enum dma_data_direction direction);

nents一定是先前传递给dma_map_sg函数的入口项的数量,而不是函数返回的DMA缓冲区的数量。

分散/聚集映射是流失DMA映射,因此适用于流式映射的规则也适用于该种映射。如果必须访问映射的分散/聚集列表,必须首先对其进行同步:

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

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

PCI双重地址周期映射

通用DMA支持层使用32位总线地址,其为设备的DMA掩码所约束。然而PCI总线还支持64位地址模式,即 双重地址周期(DAC )。通用DMA层并不支持该模式。

如果设备需要使用放在高端内存的大块缓冲区,可以考虑实现DAC支持。这种支持只有对PCI总线有效,因此必须要使用与PCI总线有关的例程。

要使用DAC,驱动程序必须包含头文件<linux/pci.h>,还必须设置一个单独的DMA掩码:

int pci_dac_set_dma_mask(struct pci_dev *pdev, u64 mask); 只有返回0时,才能使用DAC地址。

在DAC映射中使用了一个特殊类型(dma64_addr_t)。使用下面函数建立一个这样的映射:

dma64_addr_t pci_dac_page_to_dma(struct pci_dev *pdev, struct page *page, unsigned long offset, int direction);

可以只使用page结构指针来建立DAC映射,而且必须以一次一页的方式创建它们。direction参数与在通用DMA层中使用的dma_data_direction枚举类型等价,因此可以取PCI_DMA_TODEVICE、PCI_DMA_FROMDEVICE或者PCI_DMA_BIDIRECTIONAL。

DAC映射不需要显示释放它。有一套用于同步DMA缓冲区的函数,其形式如下:

void pci_dac_dma_sync_single_for_cpu(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction);

void pci_dac_dma_sync_single_for_device(struct pci_dev *pdev, dma64_addr_t dma_addr, size_t len, int direction);

一个简单的PCI DMA例子

这里提供了一个PCI设备的DMA例子(不能应用于任何真实设备),是一个假定的叫dad(DMA Acquisition Device,DMA获取设备)驱动程序的一部分,说明如何使用DMA映射:

int dad_transfer(struct dad_dev *dev, int write, void *buffer, size_t count)

{

  dma_addr_t bus_addr;

  /*映射DMA需要的缓冲区*/

  dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

  dev->dma_size = count;

  bus_addr = dma_map_single(&dev->pci_dev->dev, buffer, count, dev->dma_dir);

  

  dev_>dma_addr = bus_addr;

  

  /*设置设备*/

  writeb(dev->registers.command, DAD_CMD_DISABLEDMA);

  writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD);

  writel(dev->registers.addr, cpu_to_le32(bus_addr));

  writel(dev->registers.len, cpu_to_le32(count));

  /*开始操作*/

  writeb(dev->registers.command, DAD_CMD_ENABLEDMA);

  return 0;

}

该函数映射了准备进行传输的缓冲区并且启动设备操作。

另一半工作必须在中断服务例程中完成:

void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

  struct dad_dev *dev = (struct dad_dev *)dev_id;

  /* 确定中断是由对应的设备发来的*/

  /*释放对DMA缓冲区的映射*/

  dma_unmap_single(dev->pci_dev->dev, dev->dma_addr, dev->dma_size, dev->dma_dir);

  /* 释放之后,才能访问缓冲区,把它拷贝给用户 */

  ...

}

ISA设备的DMA

ISA总线允许两种DMA传输:本地DMA和ISA总线控制DMA。

只讨论本地(native)DMA。

本地DMA使用主板上的标准DMA控制器电路来驱动ISA总线上的信号线。

本地DMA,要关注三种实体:

  • 8237 DMA控制器(DMAC)   控制器保存了有关DMA传输的信息,如方向、内存地址、传输数据量大小等。 还包含了一个跟踪传送状态的计数器。 当控制器接收到一个DMA请求信号时,它将获得总线控制权并驱动信号线, 这样设备就能读写数据了。
  • 外围设备   当设备准备传送数据时,必须激活DMA请求信号。 DMAC负责管理实际的传输工作;当控制器选通设备后, 硬件设备就可以顺序地读/写总线上的数据。 当传输结束时,设备通常会产生一个中断。
  • 设备驱动程序   设备驱动程序完成的工作很少, 它只是负责提供DMA控制器的方向、总线地址、传输量的大小等。 它还与外围设备通信,做好传输数据的准备,当DMA传输完毕后,响应中断。

在PC中使用的早期DMA控制器能够管理四个“通道”, 每个通道都与一套DMA寄存器相关联。

DMA控制器是系统资源,因此,内核协助处理这一资源。内核使用DMA注册表为DMA通道提供了请求/释放机制, 并且提供了一组函数在DMA控制器中配置通道信息。

转载于:https://www.cnblogs.com/gjfhopeful/p/3669057.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30600197/article/details/99382769

智能推荐

linux里面ping www.baidu.com ping不通的问题_linux桥接ping不通baidu-程序员宅基地

文章浏览阅读3.2w次,点赞16次,收藏90次。对于这个问题我也是从网上找了很久,终于解决了这个问题。首先遇到这个问题,应该确认虚拟机能不能正常的上网,就需要ping 网关,如果能ping通说明能正常上网,不过首先要用命令route -n来查看自己的网关,如下图:第一行就是默认网关。现在用命令ping 192.168.1.1来看一下结果:然后可以看一下电脑上面百度的ip是多少可以在linux里面ping 这个IP,结果如下:..._linux桥接ping不通baidu

android 横幅弹出权限,有关 android studio notification 横幅弹出的功能没有反应-程序员宅基地

文章浏览阅读512次。小妹在这里已经卡了2-3天了,研究了很多人的文章,除了低版本api 17有成功外,其他的不是channel null 就是没反应 (channel null已解决)拜托各位大大,帮小妹一下,以下是我的程式跟 gradle, 我在这里卡好久又没有人可问(哭)![image](/img/bVcL0Qo)public class MainActivity extends AppCompatActivit..._android 权限申请弹窗 横屏

CNN中padding参数分类_cnn “相同填充”(same padding)-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏6次。valid padding(有效填充):完全不使用填充。half/same padding(半填充/相同填充):保证输入和输出的feature map尺寸相同。full padding(全填充):在卷积操作过程中,每个像素在每个方向上被访问的次数相同。arbitrary padding(任意填充):人为设定填充。..._cnn “相同填充”(same padding)

Maven的基础知识,java技术栈-程序员宅基地

文章浏览阅读790次,点赞29次,收藏28次。手绘了下图所示的kafka知识大纲流程图(xmind文件不能上传,导出图片展现),但都可提供源文件给每位爱学习的朋友一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长![外链图片转存中…(img-Qpoc4gOu-1712656009273)][外链图片转存中…(img-bSWbNeGN-1712656009274)]

getFullYear()和getYear()有什么区别_getyear和getfullyear-程序员宅基地

文章浏览阅读469次。Date对象取得年份有getYear和getFullYear两种方法经 测试var d=new Date;alert(d.getYear())在IE中返回 2009,在Firefox中会返回109。经查询手册,getYear在Firefox下返回的是距1900年1月1日的年份,这是一个过时而不被推荐的方法。而alert(d.getFullYear())在IE和FF中都会返回2009。因此,无论何时都应使用getFullYear来替代getYear方法。例如:2016年用 getFullYea_getyear和getfullyear

Unix传奇 (上篇)_unix传奇pdf-程序员宅基地

文章浏览阅读182次。Unix传奇(上篇) 陈皓 了解过去,我们才能知其然,更知所以然。总结过去,我们才会知道我们明天该如何去规划,该如何去走。在时间的滚轮中,许许多的东西就像流星一样一闪而逝,而有些东西却能经受着时间的考验散发着经久的魅力,让人津津乐道,流传至今。要知道明天怎么去选择,怎么去做,不是盲目地跟从今天各种各样琳琅满目前沿技术,而应该是去 —— 认认真真地了解和回顾历史。 Unix是目前还在存活的操作系_unix传奇pdf

随便推点

老赵书托(2):计算机程序的构造与解释-程序员宅基地

文章浏览阅读122次。我要推荐的第一本书便是大名鼎鼎的《Structure and Interpretation of Computer Programs》,在国内可以买到中译版,即机械工业出版社的《计算机程序的构造与解释》。 抽象豪不夸张地说,这是一本影响了好几代程序员的书。自从上世纪80年代MIT开始使用这本书作为教材开始,它使用Lisp语言——直到前两年才被Python取代,但是使用哪本教材不得而知,由这..._老赵书拖

图像处理之常见二值化方法汇总-程序员宅基地

文章浏览阅读6.1k次,点赞5次,收藏53次。图像处理之常见二值化方法汇总图像二值化是图像分析与处理中最常见最重要的处理手段,二值处理方法也非常多。越精准的方法计算量也越大。本文主要介绍四种常见的二值处理方法,通常情况下可以满足大多数图像处理的需要。主要本文讨论的方法仅针对RGB色彩空间。方法一:该方法非常简单,对RGB彩色图像灰度化以后,扫描图像的每个像素值,值小于127的将像素值设为0(黑色),值大于等于127..._web 图像二值画

基于springboot实现社区团购系统项目【项目源码+论文说明】计算机毕业设计-程序员宅基地

文章浏览阅读502次,点赞23次,收藏16次。在网站的整个开发过程中,首先对系统进行了需求分析,设计出系统的主要功能模块,其次对网站进行总体规划和详细设计,最后对基于Spring Boot的社区团购系统进行了系统测试,包括测试概述,测试方法,测试方案等,并对测试结果进行了分析和总结,进而得出系统的不足及需要改进的地方,为以后的系统维护和扩展提供了方便。现在的时代科技飞速地发展,网络交易已经深入大众的生活。项目开发的过程中,要按照规划、分期实施,特别是要注意在项目开发过程中要有条理,从点到面,一步步完善,不要贪图进度,要循环渐进的对项目进行开发。

ACwing 哈希算法入门:_ac算法 哈希-程序员宅基地

文章浏览阅读308次。哈希算法:将字符串映射为数字形式,十分巧妙,一般运用为进制数,进制据前人经验,一般为131,1331时重复率很低,由于字符串的数字和会很大,所以一般为了方便,一般定义为unsigned long long,爆掉时,即为对 2^64 取模,可以对于任意子序列的值进行映射为数字进而进行判断入门题目链接:AC代码:#include<bits/stdc++.h>using na..._ac算法 哈希

VS配置Qt和MySQL_在vs中 如何装qt5sqlmysql模块-程序员宅基地

文章浏览阅读952次,点赞13次,收藏27次。由于觉得Qt的编辑界面比较丑,所以想用vs2022的编辑器写Qt加MySQL的项目。_在vs中 如何装qt5sqlmysql模块

【渝粤题库】广东开放大学 互联网营销 形成性考核_画中画广告之所以能有较高的点击率,主要由于它具有以下特点-程序员宅基地

文章浏览阅读1k次。选择题题目:下面的哪个调研内容属于经济环境调研?()题目:()的目的就是加强与客户的沟通,它是是网络媒体也是网络营销的最重要特性。题目:4Ps策略中4P是指产品、价格、顾客和促销。题目:网络市场调研是目前最为先进的市场调研手段,没有任何的缺点或不足之处。题目:市场定位的基本参数有题目:市场需求调研可以掌握()等信息。题目:在开展企业网站建设时应做好以下哪几个工作。()题目:对企业网站首页的优化中,一定要注意下面哪几个方面的优化。()题目:()的主要作用是增进顾客关系,提供顾客服务,提升企业_画中画广告之所以能有较高的点击率,主要由于它具有以下特点

推荐文章

热门文章

相关标签