目录

概述

DMA映射

建立一致性DMA映射

DMA池

建立流式DMA映射

单页流式映射

分散/聚集映射

PCI双重地址周期映射 DAC

简单的PCI DMA例子

ISA设备DMA


概述

DMA的操作是需要物理地址的,但是在linux内核中使用的都是虚拟地址,如果想要用DMA对一段内存进行操作,如何得到这一段内存的物理地址和虚拟地址的映射呢?dma_alloc_coherent这个函数实现了这种机制。

1、函数原型: void *dma_alloc_coherent( struct device *dev, size_t size,dma_addr_t *dma_handle,gfp_t gfp);

2、调用

A = dma_alloc_writecombine(B,C,D,GFP_KERNEL);

含义:

A: 内存的虚拟起始地址,在内核要用此地址来操作所分配的内存

B: struct device指针,可以平台初始化里指定,主要是dma_mask之类,可参考framebuffer

C: 实际分配大小,传入dma_map_size即可

D: 返回的内存物理地址,dma就可以用。

所以,A和D是一一对应的,只不过,A是虚拟地址,而D是物理地址。对任意一个操作都将改变缓冲区内容。

此函数的理解是,调用此函数将会分配一段内存,D将返回这段内存的实际物理地址供DMA来使用,A将是D对应的虚拟地址供操作系统调用,对A和D的的任意一个进行操作,都会改变这段内存缓冲区的内容。

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中。

向系统返回缓冲区
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>

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是该池分配操作所必须遵守的硬件对齐原则。

销毁DMA池
void dma_pool_destroy(struct dma_pool *pool);

DMA池分配内存
void *dma_pool_alloc(struct dma_pool *pool,int mem_flags, dma_addr_t *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获得的用户空间缓冲区。

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

分散/聚集映射

有几个缓冲区,它们需要与设备双向传输数据。

可以简单地依次映射每一个缓冲区并且执行请求的操作, 但是一次映射整个缓冲区表还是很有利的。

映射分散表的第一步是建立并填充一个描述被传输缓冲区的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);
解除
void dma_unmap_sg(struct device *dev,struct scatterlsit *list, int nents,enum dma_data_direction direction);

PCI双重地址周期映射 DAC

通用DMA支持层使用32位总线地址,然而PCI总线还支持64位地址模式,即双重地址周期(DAC)。
<linux/pci.h>
通用DMA层并不支持该模式。

要使用PCI总线的DAC,必须设置一个单独的DMA掩码:
int pci_dac_set_dma_mask(struct pci_dev *pdev,u64 mask);

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

简单的PCI DMA例子

这里提供了一个PCI设备的DMA例子dad(DMA Acquisition Device)的一部分,说明如何使用DMA映射:

int dad_transfer(struct dad_dev *dev,int write,void *buffer,size_t count)
{dma_addr_t bus_addr;dev->dma_dir = (write ? DMA_TO_DEVICE : DMA_FROM_DEVICE);dev->dma_size = count;/*映射DMA需要的缓冲区*/bus_addr = dma_map_single(&dev->pci_dev->dev,buffer,count,dev->dma_dir);writeb(dev->registers.command,DAD_CMD_DISABLEDMA);writeb(dev->registers.command,write ? DAD_CMD_WR : DAD_CMD_RD);writeb(dev->registers.addr,cpu_to_le32(bus_addr)); /*设置*/writeb(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_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控制器中配置通道信息。

关于dma_alloc_coherent的用法相关推荐

  1. 内核使用硬件ip的dma,dma_alloc_coherent 与 dma_alloc_writecombine (转)

    内核的dma一般在平台初始化的时候已经分配好了.但是对于一些有内部dma的硬件ip,比如usb ip.video加速ip,他们可能由ip厂商封装好的,没办法绑定到cpu端,这时候在内核使用dma就要注 ...

  2. dma_alloc_coherent 申请内存用法和问题总结

    文章目录 1.dma_alloc_coherent用法 2.问题 3.解决方法 方法一,走CMA空间配置 3.1 内核配置``CONFIG_CMA`` 3.2 修改cma起始地址 3.3 设置cma空 ...

  3. c语言中external,static关键字用法

    static用法: 在C中,static主要定义全局静态变量.定义局部静态变量.定义静态函数. 1.定义全局静态变量:在全局变量前面加上关键字static,该全局变量变成了全局静态变量.全局静态变量有 ...

  4. Pandas_transform的用法

    先来看一个实例问题. 如下销售数据中展现了三笔订单,每笔订单买了多种商品,求每种商品销售额占该笔订单总金额的比例.例如第一条数据的最终结果为:235.83 / (235.83+232.32+107.9 ...

  5. Python中yield和yield from的用法

    yield 后面接的是 future 对象 调用方 委托生成器 yield from 直接给出循环后的结果 yield from 委托者和子生成器直接通信 yield from 直接处理stopIte ...

  6. pytorch学习 中 torch.squeeze() 和torch.unsqueeze()的用法

    squeeze的用法主要就是对数据的维度进行压缩或者解压. 先看torch.squeeze() 这个函数主要对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的 ...

  7. python yield 和 yield from用法总结

    #例1. 简单输出斐波那契數列前 N 个数 #缺点:该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列 #要提高 fab 函数的可复用性,最好不要直接打印出数列,而 ...

  8. tf.nn.embedding_lookup()的用法

    函数: tf.nn.embedding_lookup( params, ids, partition_strategy='mod', name=None, validate_indices=True, ...

  9. OpenMP用法大全

    OpenMP基本概念 OpenMP是一种用于共享内存并行系统的多线程程序设计方案,支持的编程语言包括C.C++和Fortran.OpenMP提供了对并行算法的高层抽象描述,特别适合在多核CPU机器上的 ...

最新文章

  1. centos6.2下配置nfs
  2. RNN梯度消失和爆炸的原因 以及 LSTM如何解决梯度消失问题
  3. 计算机桌面图标有背影,桌面图标有背影怎么解决
  4. 内存管理范围和@property
  5. c++面试常用知识(sizeof计算类的大小,虚拟继承,重载,隐藏,覆盖)
  6. 猪和兔子的玻璃体给人用(仅仅是个人想法)
  7. LoRaWAN 巩固了其作为低功耗广域网主导技术的地位
  8. 「分块系列」数列分块入门3 解题报告
  9. C++11多线程---future和promise
  10. 解决bootstrap dropdown 下拉菜单有时候不能显示的问题
  11. Arcgis javascript那些事儿(十四)——连接oracle
  12. 开发OA产品的部分网站
  13. ”数独“android小游戏
  14. arp计算机病毒解决办法,“ARP病毒的解决方案”的解决方案
  15. [转载]使用 Abbot 框架自动化测试 Eclipse 插件的用户界面,第 2 部分
  16. 2021年秋季Python程序设计相关课程教材推荐
  17. 使用UCSC基因组浏览器可视化测序深度分布数据
  18. 面试前需要注意的细节点(有需要的朋友可以看看)
  19. 力扣刷题 DAY_69 回溯
  20. 用dw 删除重复html文件,Dreamweaver怎么撤销重做,DW怎么返回上一步,看完就明白了...

热门文章

  1. 女生转行做什么工作好?想要转行互联网可以选择哪些方向?
  2. 重庆顶香味分析快手春竹笋的制作方法
  3. 仿抖音效果的数字时钟罗盘
  4. kali 中 MongoDB安装
  5. 腾讯云-即时通讯 IM
  6. 计算机语言圆周率,使用Java如何计算圆周率
  7. 华为千元旗舰迎GPU Turbo会员不限量升级,这件事很“吓人”
  8. UVA10306 - e-Coins(二维完全背包)
  9. 【Python性能优化实例】计算 numpy 数组首尾为 0 的数量
  10. 字符串全排列算法_C#版_剑指OFFER