DMABUF, DMA mapping,IOMMU的区别_dma mapper iommu-程序员宅基地

1. DMABUF can be used as a wrapperto encapsulate other memory management frameworks. All these memory managementframework(I mean mostly for graphics), buffer is the keypoint. DMABUF defines astandard buffer structure. So DMABUF can be used as a wrapper forTTM/GEM/Android ION... and etc. Notice DMABUF can't replace these things, causeit doesn't cover everything. E.g: DMABUF has no userspace interfaces, right nowonly kernel interfaces(can be used in device driver).

(DMABUF被使用给其他的memory management framework(ION))

2. Kernel has DMA mapping API fromorigin. ARM defines IOMMU which can be used to connect scattered physicalmemory as a continuous region for devices which needs continue address towork(e.g: DMA). So IOMMU implementations & CMA should work behind kernelDMA mapping API. E.g: dma_alloc_from_contiguous can be implemented by CMA;dma_alloc_coherent can be implemented by IOMMU or by the normal case(just call__get_free_pages). So for device drivers need dma buffers, we should use dmamapping APIs, not call iommu api directly.

(device drivers先使用dma mapping APIs(它调用IOMMU机制的函数))

3. For tegra, GART & SMMU canbe used to implement IOMMU apis.

1,mmap系统调用可以实现将设备内存映射到用户进程的地址空间。

2,使用get_user_pages,可以把用户空间内存映射到内核中。

3,DMA的I/O操作,使得外设具有直接访问系统内存的能力。

-------------
内存管理

内核用来管理内存的数据结构

---------
地址内型

Linux是一个虚拟内存系统,即用户程序使用的地址与硬件使用的物理地址是不等同的。

虚拟内存引入了一个间接层,使得许多操作成为可能:
*有了虚拟内存,系统中运行的程序可以分配比物理内存更多的内存。
*虚拟地址还能让程序在进程的地址空间内使用更多的技巧,包括将程序的内存映射到设备内存上。

地址内型列表
*用户虚拟地址 每个进程都有自己的虚拟地址空间。
*物理地址 处理器访问系统内存时使用的地址。
*总线地址 在外围总线和内存之间使用。MMU可以实现总线和主内存之间的重新映射。
           当设置DMA操作时,编写MMU相关的代码是一个必需的步骤。
*内核逻辑地址
           内核逻辑地址组成了内核的常规地址空间,该地址映射了部分(或全部)内存,
           并经常被视为物理地址。在大多数体系架构中,逻辑地址与其相关联的物理地址
           的不同,仅仅在于它们之间存在一个固定的偏移量。kmalloc返回的内存就是
           内核逻辑地址。
*内核虚拟地址
           内核虚拟地址与逻辑地址相同之处在于,都将内核空间的地址映射到物理地址上。
           不同之处在于,内核虚拟地址与物理地址的映射不是线性的和一对一的。
           vmalloc返回一个虚拟地址,kmap函数也返回一个虚拟地址。

------------------
物理地址和页

物理地址被分为离散的单元,称之为页。

系统内部许多对内存的操作都是基于单个页的。

大多数系统都使用每页4096个字节,PAGE_SIZE <asm/page.h>给出指定体系架构下的页大小。

观察内存地址,无论是虚拟的还是物理的,它们都被分为页号和一个页内的偏移量。
如果每页4096个字节,那么最后的12位就是偏移量,剩余的高位则指定页号。

页帧数:将除去偏移量的剩余位移到右端,称该结果为页帧数。

-------------------
高端与低端内存

内核(在x86架构中)将4GB的虚拟地址空间分割为用户空间和内核空间。
一个典型的分割是将3GB分配给用户空间,1GB分配给内核空间。

占用内核地址空间最大的部分是物理内存的虚拟映射,
内核无法直接操作没有映射到内核地址空间的内存。

低端内存:
         只有内存的低端部分拥有逻辑地址。内核的数据结构必须放置在低端内存中。
高端内存:
         除去低端内存的剩余部分没有逻辑地址。它们处于内核虚拟地址之上。

--------------------
内存映射和页结构

内核使用逻辑地址来引用物理内存中的页。

为解决在高端内存中无法使用逻辑地址的问题,内核中处理内存的函数趋向于使用
指向page结构的指针<linux/mm.h>。

page结构用来保存内核需要知道的所有物理内存信息,对系统中的每个物理页,
都有一个page结构相对应。

-----
page结构的几个成员:

atomic_t count; 对该页的访问计数。
void *virtual; 如果页面被映射,则指向页的内核虚拟地址;
                如果未被映射,则为NULL。
                低端内存页总是被映射,而高端内存页通常不被映射。
unsigned long flags; 描述页状态的一系列标志。
                      PG_locked表示内存中的页已经被锁住,
                      而PG_reserved表示禁止内存管理系统访问该页。
-----
内核维护了一个或者多个page结构的数组,用来跟踪系统中的物理内存。
-----
有一些函数和宏用来在page结构指针与虚拟地址之间进行转换:

struct page *virt_to_page(void *kaddr); <asm/page.h>
将内核逻辑地址转换为响应的page结构指针。
struct page *pfn_to_page(int pfn);
针对给定的页帧号,返回page结构指针。
void *page_address(struct page *page); <linux/mm.h>
如果地址存在的话,则返回页的内核虚拟地址。
void *kmap(struct page *page); <linux/highmem.h>
为系统中的页返回内核虚拟地址。
对于低端内存页,它只返回页的逻辑地址;
对于高端内存页,kmap在专用的内核地址空间创建特殊的映射。
void kunmap(struct page *page);
释放由kmap创建的映射。
void *kmap_atomic(struct page *page,enum km_type type);
void kunmap_atomic(void *addr,enum km_type type);
<linux/highmem.h>,<asm/kmap_types.h>
是kmap的高性能版本。

-------
页表

在任何现代的系统中,处理器必须使用某种机制,将虚拟地址转化为响应的物理地址,
这种机制成为页表。

-------
虚拟内存区

虚拟内存区(VMA)用于管理进程地址空间中不同区域的内核数据结构。

可以将其描述为“拥有自身属性的内存对象”。

进程的内存映射包含下面这些区域:
*可执行代码区域
*多个数据区:初始化数据,非初始化数据(BSS),程序堆栈。
*与每个活动的内存映射对应的区域

#cat /proc/1/maps
可以了解进程的内存区域。
/proc/self始终指向当前进程。

每行都是用下面的形式表示的:
start-end perm offset major:minor inode image
在/proc/*/maps中的每个成员(除映像名外)都与vm_area_struct结构中的一个成员对应:
start
end
该内存区域的起始处和结束处的虚拟地址。
perm
读、写和执行权限,最后一位若是p表示私有,s表示共享。
offset
内存区域在映射文件中的起始位置。
major
minor
拥有映射文件的设备的主设备号和次设备号。
inode
被映射的文件的索引节点号。
image
被映射文件的名称。

-------------
vm_area_struct结构

当用户空间进程调用mmap,将设备内存映射到它的地址空间时,
系统通过创建一个表示该映射的新VMA作为响应。

支持mmap的驱动程序需要帮助进城完成VMA的初始化。

vm_area_struct结构是在<linux/mm.h>中定义的。

VMA的主要成员如下:
unsigned long vm_start;
unsigned long vm_end;
该VMA所覆盖的虚拟地址范围。
struct file *vm_file;
指向与该区域相关联的file结构指针。
unsigned long vm_pgoff;
以页为单位,文件中该区域的偏移量。
unsigned long vm_flags;
描述该区域的一套标志。
struct vm_operations_struct *vm_ops;
内核能调用的一套函数,用来对该内存区进行操作。
它的存在表示内存区域是一个内核“对象”。
void *vm_private_data;
驱动程序用来保存自身信息的成员。

vm_operations_struct结构的几个成员:

void (*open)(struct vm_area_struct *vma);
void (*close)(struct vm_area_struct *vma);
struct page *(*nopage)(struct vm_area_struct *vma,
              unsigned long address,int *type);
int (*populate)(struct vm_area_struct *vm,
                unsigned long address,unsigned long len,
                pgprot_t prot,unsigned long pgoff,int nonblock);

--------------------------
内存映射处理

在系统中的每个进程都拥有一个struct mm_struct结构<linux/sched.h>,
其中包含了虚拟内存区域链表、页表以及其他大量内存管理信息,
还包含一个信号灯(mmap_sem)和一个自旋锁(page_table_lock)。

-----
mmap设备操作

对于驱动程序来说,内存映射可以提供给用户程序直接访问设备内存的能力。

映射一个设备意味着将用户空间的一段内存与设备内存关联起来。
无论何时当程序在分配的地址范围内读写时,实际上访问的就是设备。

要注意:
像串口和其它面向流的设备不能进行mmap抽象。
必须以PAGE_SIZE为单位进行映射。

---------
rmpap_pfn_range和io_remap_page_range为一段物理地址建立新的页表。

int remap_pfn_range(struct vm_area_struct *vm,
                    unsigned long virt_addr,unsigned long pfn,
                    unsigned long size,pgprot_t prot);
int io_remap_page_range(struct vm_area_struct *vma,
                        unsigned long virt_addr,unsigned long phys_addr,
                        unsigned long size,pgprot_t prot);
vma:虚拟内存区域,在一定范围内的页将被映射到该区域内。
virt_addr:重新映射时的起始用户虚拟地址。该函数为处于virt_addr和virt_addr+size
          之间的虚拟地址建立页表。
pfn:与物理内存对应的页帧号,虚拟内存将要被映射到该物理内存上。
size:以字节为单位,被重新映射的区域大小。
prot:新VMA要求的"保护"属性。

------------
一个简单的实现

static int simple_remap_mmap(struct file *filp,struct vm_area_struct *vma)
{
if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,
                           vma->vm_end-vma->vm->vm_start,vma->vm_page_prot))
   return -EAGAIN;

vma->vm_ops = &simple_remap_vm_ops;
simple_vma_open(vma);
return 0;
}

可见,重新映射内存就是调用remap_pfn_range函数创建所需的页表。

--------------
为VMA添加操作

vm_area_struct结构包含了一系列针对VMA的操作。

void simple_vma_open(struct vm_area_struct *vma)
{
printk(KERN_NOTICE "Simple VMA open,virt %lx,phys %lx\n",
               vma->vm_start,vma->vm_pgoff << PAGE_SHIFT);
}

void simple_vma_close(struct vm_area_struct *vma)
{
printk(KERN_NOTICE "Simple VMA close.\n");
}

static struct vm_operations_struct simple_remap_vm_ops =
{
.open = simple_vma_open,
.close = simple_vma_close,
};


----------------
使用nopage映射内存

有时驱动程序对mmap的实现必须具有更好的灵活性,在这种情况下,
提倡使用VMA的nopage方法实现内存映射。

如果要支持mremap系统调用,就必须实现nopage函数。

struct page *(*nopage)(struct vm_area_struct *vma,
                       unsigned long address,int *type);

-----------------
重映射特定的I/O区域

一个典型的驱动程序只映射与其外围设备相关的一小段地址,而不是映射全部地址。

-----------------
重新映射RAM

--
使用nopage方法重映射RAM
--
重新映射内核虚拟地址

-------------------
执行直接I/O访问

实现直接I/O的关键是get_user_pages()函数:<linux/mm.h>

int get_user_pages(struct task_struct *tsk,struct mm_struct *mm,
                   unsigned long start,int len,int write,int force,
                   struct page **pages,struct vm_area_struct **vmas);
----------
异步I/O

<linux/aio.h>
ssize_t (*aio_read)(),ssize_t (*aio_write)(),ssize_t (*aio_fsync)()

------------------------------------------------------------
直接内存访问      DMA

DMA是一种硬件机制,它允许外围设备和主内存之间直接传输它们的I/O数据,
而不需要系统处理器的参与。

--------------------------
DMA数据传输概览

有两种方式引发数据传输:
1,软件对数据的请求,比如通过read函数。
2,硬件异步地将数据传递给系统。

第一种情况的步骤如下:
1)当进程调用read,驱动程序分配一个DMA缓冲区,
并让硬件将数据传输到这个缓冲区中。进程处于睡眠状态。
2)硬件将数据写入到DMA缓冲区中,当写入完毕,产生一个中断。
3)中断处理程序获得输入的数据,应答中断,并且唤醒进程,
该进程即可读取数据。

第二种情况发生在异步使用DMA时。
比如对于一个数据采集设备,即使没有进程读取数据,它也不断地写入数据。
此时,驱动程序应该维护一个缓冲区,其后的read调用将返回所有积累的数据给用户空间。
这种传输方式的步骤如下:
1)硬件产生中断,宣告新数据的到来。
2)中断处理程序分配一个缓冲区,并且告诉硬件向哪里传输数据。
3)外围设备将数据写入缓冲区,完成后产生另外一个中断。
4)处理程序分发新数据,唤醒任何相关进程,然后执行清理工作。

高效的DMA处理依赖于中断报告!!!


------------------
分配DMA缓冲区

使用DMA缓冲区的主要问题是:当大于一页时,它们必须占据连续的物理页,
这是因为使用ISA或者PCI系统总线传输数据,而这两种方式使用的都是物理地址。

驱动程序作者必须谨慎地为DMA操作分配正确的内存类型,因为
并不是所有内存区间都适合DMA操作。

在实际操作中,一些设备和一些系统中的高端内存不能用于DMA,
这是因为外围设备不能使用高端内存的地址。

对于有限制的设备,应使用GFP_DMA标志调用kmalloc或者get_free_pages从
DMA区间分配内存。另外,还可以通过使用通用DMA层来分配缓冲区。

-------------------
总线地址

使用DMA的设备驱动程序将与连接到总线接口上的硬件通信,
硬件使用的是物理地址,而程序代码使用的是虚拟地址。

实际上,基于DMA的硬件使用总线地址,而非物理地址。

<asm/io.h>的一些函数提供了可移植的方案:
unsigned long virt_to_bus(volatile void *address);
void *bus_to_virt(unsigned long address);
这些函数在内核逻辑地址和总线地址间执行了简单的转换。

-----------------------------------
通用DMA层

DMA操作最终会分配缓冲区,并将总线地址传递给设备。
内核提供了一个与总线---体系架构无关的DMA层,它会隐藏大多数问题。
在编写驱动程序时,为DMA操作使用该层。

device结构
该结构是在Linux设备模型中用来表示设备底层的,驱动程序通常不直接使用该结构,
但是,在使用通用DMA层时,需要使用它。

该结构内部隐藏了描述设备的总线细节。 <linux/dma-mapping.h>

-----------------
处理复杂的硬件

是否给定的设备在当前主机上具备执行DMA操作的能力?
因为有的设备受限于24位寻址。可以用dma_set_mask()函数解决。

--------------------------
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双重地址周期映射

通用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
<asm/dma.h>

int request_dma(unsigned int channel,const char *name);
void free_dma(unsigned int channel);

在open操作时请求通道,比在模块初始化函数中请求通道要好一些。

注意:
使用DMA的每个设备需要一根IRQ线,否则它将无法通知数据已经传输完毕。

open
------
int dad_open(struct inode *inode,struct file *filp)
{
sturct dad_device *my_device;

...

if((error = request_irq(my_device.irq,dad_interrupt,
                                SA_INTERRUPT,"dad",NULL)))
   return error;

if((error = request_dma(my_device.dma,"dad")))
{
   free_irq(my_device.irq,NULL);
   return error;
}

...
return 0;
}

close
-------
void dad_close(struct inode *inode,struct file *filp)
{
struct dad_device *my_device;
...
free_dma(my_device.dma);
free_irq(my_device.irq,NULL);
...
}

-------------------------
与DMA控制器通信

注册之后,驱动程序的主要任务包括为适当的操作配置DMA控制器。

幸运的是,内核导出了驱动程序所需要的所有函数。

当read或者write函数被调用时,或者准备异步传输时,
驱动程序都要对DMA控制器进行配置。

unsigned long claim_dma_lock(); 获得DMA自旋锁
void release_dma_lock(unsigned long flags); 返回DMA自旋锁

<asm/dma.h>

void set_dma_mode(unsigned int channel,char mode);
表明是从设备读入通道(DMA_MODE_READ),还是向设备写入数据(DMA_MODE_WRITE)。
void set_dma_addr(unsigned int channel,unsigned int addr);
为DMA缓冲区分配地址。该函数将addr的最低24位存储到控制器中。
addr参数必须是总线地址。
void set_dma_count(unsigned int channel,unsigned int count);
为传输的字节量赋值。
void disable_dma(unsigned int channel);
控制器内的DMA通道可以被禁用。
void enable_dma(unsigned int channel);
该函数告诉控制器,DMA通道包含了合法的数据。
int get_dma_residue(unsigned int channel);
驱动程序有时需要知道DMA传输是否已经结束。
该函数返回还未传输的字节数。
void clear_dma_ff(unsigned int channel);
该函数清除了DMA的触发器。

使用这些函数,驱动程序可以实现如下函数,为DMA传输做准备:
----------------------------
int dad_dma_prepare(int channel,int mode,unsigned int buf,
                    unsigned int count)
{
unsigned long flags;

flags = claim_dma_lock();

disable_dma(channel);

clear_dma_ff(channel);

set_dma_mode(channel,mode);
set_dma_addr(channel,virt_to_bus(buf));
set_dma_count(channel,count);

enable_dma(channel);

release_dma_lock(flags);

return 0;
}
下面代码用来检验是否完成DMA:
int dad_dma_isdone(int channel)
{
int residue;

unsigned long flags = claim_dma_lock();

residue = get_dma_residue(channel);

release_dma_lock(flags);

return (residue == 0);
}

--------------------------------
剩下需要做的事情是配置设备板卡,硬件手册是程序员唯一的朋友。

-------------------------------------


DMA-BUF API使用指南

by JHJ([email protected])

转载出自:http://blog.csdn.net/crazyjiang

本文将会告诉驱动开发者什么是dma-buf共享缓冲区接口,如何作为一个生产者及消费者使用共享缓冲区。

任何一个设备驱动想要使用DMA共享缓冲区,就必须为缓冲区的生产者或者消费者。

如果驱动A想用驱动B创建的缓冲区,那么我们称B为生成者,A为消费者。

生产者:

  • 实现和管理缓冲区的操作函数[1];
  • 允许其他消费者通过dma-buf接口函数共享缓冲区;
  • 实现创建缓冲区的细节;
  • 决定在什么存储设备上申请内存;
  • 管理scatterlist的迁徙;

消费者:

  • 作为一个缓冲区的消费者;
  • 无需担心缓冲区是如何/在哪里创建的;
  • 需要一个可以访问缓冲区scatterlist的机制,将其映射到自己的地址空间,这样可以让自己可以访问到内存的同块区域,实现共享内存。

数据结构

dma_buf是核心数据结构,可以理解为生产者对象。

struct dma_buf {
        size_t size;
        struct file *file;
        struct list_head attachments;
        const struct dma_buf_ops *ops;
        /* mutex to serialize listmanipulation and attach/detach */
        struct mutex lock;
        void *priv;
};

其中
size
为缓冲区大小
file
为指向共享缓冲区的文件指针
attachments
为附着在缓冲区上的设备(消费者)
ops
为绑定在该缓冲区的操作函数
priv
为生产者的私有数据

dma_buf_attachment可以理解为是消费者对象。

structdma_buf_attachment {
        struct dma_buf *dmabuf;
        struct device *dev;
        struct list_head node;
        void *priv;
};

其中
dmabuf
为该消费者附着的共享缓冲区
dev
为设备信息
node
为连接其他消费者的节点
priv
为消费者私有数据

这两个数据结构的关系如下所示。

 

外设的dma-buf操作函数

dma_buf共享缓冲区接口的使用具体包括以下步骤:

  1. 生产者发出通知,其可以共享一块缓冲区;
  2. 用户空间获取与该共享缓冲区关联的文件描述符,将其传递给潜在的消费者;
  3. 每个消费者将其绑定在这个缓冲区上;
  4. 如果需要,缓冲区使用者向消费者发出访问请求;
  5. 当使用完缓冲区,消费者通知生产者已经完成DMA传输;
  6. 当消费者不再使用该共享内存,可以脱离该缓冲区;

 

1.   生产者共享缓冲区

消费者发出通知,请求共享一块缓冲区。

struct dma_buf *
dma_buf_export
(void *priv, struct dma_buf_ops *ops,size_t size, int flags)

如果函数调用成功,则会创建一个数据结构dma_buf,返回其指针。同时还会创建一个匿名文件绑定在该缓冲区上,因此这个缓冲区可以由其他消费者共享了(实际上此时缓冲区可能并未真正创建,这里只是创建了一个抽象的dma_buf)。

2.   用户空间获取文件句柄并传递给潜在消费者

用户程序请求一个文件描述符(fd),该文件描述符指向和缓冲区关联的匿名文件。用户程序可以将文件描述符共享给驱动程序或者用户进程程序。

int 
dma_buf_fd
(struct dma_buf *dmabuf)

该函数创建为匿名文件创建一个文件描述符,返回"fd"或者错误。

3.   消费者将其绑定在缓冲区上

现在每个消费者可以通过文件描述符fd获取共享缓冲区的引用。

struct dma_buf *
dma_buf_get
(int fd)

该函数返回一个dma_buf的引用,同时增加它的refcount(该值记录着dma_buf被多少消费者引用)。

获取缓冲区应用后,消费者需要将它的设备附着在该缓冲区上,这样可以让生产者知道设备的寻址限制。

struct dma_buf_attachment *
dma_buf_attach(struct dma_buf *dmabuf, struct device*dev)

该函数返回一个attachment的数据结构,该结构会用于scatterlist的操作。

dma-buf共享框架有一个记录位图,用于管理附着在该共享缓冲区上的消费者。

到这步为止,生产者可以选择不在实际的存储设备上分配该缓冲区,而是等待第一个消费者申请共享内存。

4.   如果需要,消费者发出访问该缓冲区的请求

当消费者想要使用共享内存进行DMA操作,那么它就会通过接口dma_buf_map_attachment来访问缓冲区。在调用map_dma_buf前至少有一个消费者与之关联。

struct sg_table * 
dma_buf_map_attachment(struct dma_buf_attachment *, enumdma_data_direction);

该函数是dma_buf->ops->map_dma_buf的一个封装,它可以对使用该接口的对象隐藏"dma_buf->ops->"

 struct sg_table * 
(*
map_dma_buf)(struct dma_buf_attachment *, enumdma_data_direction);

生产者必须实现该函数。它返回一个映射到调用者地址空间的sg_table,该数据结构包含了缓冲区的scatterlist

如果第一次调用该函数,生产者现在可以扫描附着在共享缓冲区上的消费者,核实附着设备的请求,为缓冲区选择一个合适的物理存储空间。

基于枚举类型dma_data_direction,多个消费者可能同时访问共享内存(比如读操作)。

如果被一个信号中断,map_dma_buf()可能返回-EINTR

5.   当使用完成,消费者通知生成者DMA传输结束

当消费者完成DMA操作,它可以通过接口函数dma_buf_unmap_attachment发送“end-of-DMA”给生产者。

void 
dma_buf_unmap_attachment(struct dma_buf_attachment *, structsg_table *);

该函数是dma_buf->ops->unmap_dma_buf()的封装,对使用该接口的对象隐藏"dma_buf->ops->"

dma_buf_ops结构中,unmap_dma_buf定义成

void 
(*
unmap_dma_buf)(struct dma_buf_attachment *, structsg_table *);

unmap_dma_buf意味着消费者结束了DMA操作。生产者必须要实现该函数。

6.   当消费者不再使用该共享内存,则脱离该缓冲区;

当消费者对该共享缓冲区没有任何兴趣后,它应该断开和该缓冲区的连接。

a.  首先将其从缓冲区中分离出来。

void 
dma_buf_detach(struct dma_buf *dmabuf, structdma_buf_attachment *dmabuf_attach);

此函数从dmabufattachment链表中移除了该对象,如果消费者实现了dma_buf->ops->detach(),那么它会调用该函数。

b.  然后消费者返回缓冲区的引用给生产者。

void 
dma_buf_put(struct dma_buf *dmabuf);

该函数减小缓冲区的refcount

如果调用该函数后refcount变成0,该文件描述符的"release"函数将会被调用。它会调用dmabuf->ops->release(),企图释放生产者为dmabuf申请的内存。

注意事项:

a. attach-detach{map,unmap}_dma_buf成对执行非常重要。

attach-detach函数调用可以让生产者明确当前消费者对物理内存的限制。如果可能,它会在不同的存储设备上申请或/和移动物理页框。

b.  如果有必要,需要将缓冲区移动到另一个物理地址空间。

如果

  • 至少有一个map_dma_buf存在,
  • 该缓冲区已经分配了物理内存,

此时另一个消费者打算使用该缓冲区,生产者可能允许其请求。

如果生产者允许其请求:

如果新的消费者有严格的DMA寻址限制,而且生产者可以处理这些限制,那么生产者会在map_dma_buf里等待剩余消费者完成缓冲区访问。一旦所有消费者都完成了访问并且unmap了缓冲区,生产者可以将该缓冲区转移到严格的物理地址空间,然后再次允许{map,unmap}_dma_buf操作移动后的共享缓冲区。

如果生产者不能满足新消费者的寻址限制,调用dma_buf_attach()则会返回失败。

内核处理器访问dma-buf缓冲区对象

允许处理器在内核空间作为一个消费者访问dma-buf对象的原因如下:

  • 撤销/回退操作。比如一个设备连接到USB总线上,在发送数据前内核需要将第一个数据移除。
  • 对其他消费者而言这个是全透明的。比如其他用户空间消费者注意不到一个 dma-buf是否做过一次撤销/回退操作。

在内核上下文访问dma_buf需要下面三个步骤:

1.  访问前的准备工作,包括使相关cache无效,使处理器可以访问缓冲区对象;

2.  通过dma_buf map接口函数以页为单位访问对象;

3.  完成访问时,需要刷新必要的处理器cache,释放占用的资源;

1.   访问前的准备工作

处理器在内核空间打算访问dma_buf对象前,需要通知生产者。

int 
dma_buf_begin_cpu_access
(struct dma_buf *dmabuf, size_t start,size_t len,
                               enum dma_data_direction direction)

生产者可以确保处理器可以访问这些内存缓冲区,生产者也需要确定处理器在指定区域及指定方向的访问是一致性的。生产者可以使用访问区域及访问方向来优化cache flushing。比如访问指定范围外的区域或者不同的方向(用读操作替换写操作)会导致陈旧的或者不正确的数据(比如生产者需要将数据拷贝到零时缓冲区)。

该函数调用可能会失败,比如在OOM(内存紧缺)的情况下。

2.   访问缓冲区

为了支持处理器可以访问到驻留在高端内存中的dma_buf对象,需要调用一个和kmap类似的接口函数。访问dma_buf需要页对齐。在访问对象前需要先做映射工作,及需要得到一个内核虚拟地址。操作完后,需要取消该对象的映射。

void *
dma_buf_kmap(struct dma_buf *, unsigned long);

void 
dma_buf_kunmap(struct dma_buf *, unsigned long, void*);

该函数有对应的原子操作函数,如下所示。在调用原子操作函数时,生产者和消费者都不能被阻塞。

void *
dma_buf_kmap_atomic(struct dma_buf *, unsigned long);

void 
dma_buf_kunmap_atomic(struct dma_buf *, unsigned long, void*);

生产者在同一时间不能同时调用原子操作函数(在任何进程空间)。

如果访问缓冲区区域不是页对齐的,虽然kmap对应的区域数据得到了更新,但是在这个区域附近的区域数据也相应得到了更新,这个不是我们所希望的。也就是说kmap更新了自己关心的区域外,还更新了其他区域,对于那些区域的使用者来说,数据就已经失效了。

下图给出了一个例子,一共有四个连续的页,其中kmap没有页对齐获取部分缓冲区,即红色部分,由于会同步cache,其附近的区域数据也会被更新,被更新区域的范围和cache行的大小有关系。

注意这些调用总是成功的,生产者需要在begin_cpu_access中完成所有的准备,在这其中可能才会有失败。

3.   完成访问

当消费者完成对begin_cpu_access指定范围内的缓冲区访问,需要通知生产者(刷新cache,同步数据集释放资源)。

void dma_buf_end_cpu_access(struct dma_buf *dma_buf,
                                       size_t start, size_t len,
                                       enum dma_data_direction dir);

用户空间通过mmap直接访问缓冲区

在用户空间映射一个dma-buf对象,主要有两个原因:

  • 处理器回退/撤销操作;
  • 支持消费者程序中已经存在的mmap接口;

1.  处理器在一个pipeline中回退/撤销操作

在处理pipeline过程中,有时处理器需要访问dma-buf中的数据(比如创建thumbnail, snapshots等等)。用户空间程序通过使用dma-buf的文件描述符fd调用mmap来访问dma-buf中的数据是一个好办法,这样可以避免用户空间程序对共享内存做一些特殊处理。

进一步说AndroidION框架已经实现了该功能(从用户空间消费者来说它实现了一个和dma-buf很像的东西,使用fds用作文件句柄)。因此实现该功能对于Android用户空间来说是有意义的。

没有特别的接口,用户程序可以直接基于dma-buffd调用mmp

2.  支持消费者程序中已经存在的mmap接口

与处理器在内核空间访问dma-buf对象目的一样,用户空间消费者可以将生产者的dma-buf缓冲区对象当做本地缓冲区对象一样使用。这对drm特别重要,其OpenglX的用户空间及驱动代码非常巨大,重写这部分代码让他们用其他方式的mmap,工作量会很大。

int 
dma_buf_mmap(struct dma_buf *, struct vm_area_struct*, unsigned long);

参考文献

[1] structdma_buf_ops in include/Linux/dma-buf.h 
[2] All interfaces mentioned above defined in include/linux/dma-buf.h
 
[3]
 https://lwn.net/Articles/236486/ 
[4] Documentation/dma-buf-sharing.txt

 

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

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签