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

智能推荐

133道Java面试题及答案(面试必看)-程序员宅基地

文章浏览阅读1.7w次,点赞6次,收藏118次。Java 面试随着时间的改变而改变。在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来越高级,面试官问的问题也更深入。 在我初入职场的时候,类似于 Vector 与 Array 的区别、HashMap 与 Hashtable 的区别是最流行的问题,只需要记住它们,就能在面试中获得更好的机会,但这种情形已经不复存在。如今,你...

tensorflow2系类知识-3 :CNN_cnn_class-程序员宅基地

文章浏览阅读240次。CNN基础知识卷积神经网络(Convolutional Neural Network, CNN)是一种结构类似于人类或动物的 视觉系统 的人工神经网络,包含一个或多个卷积层(Convolutional Layer)、池化层(Pooling Layer)和全连接层(Fully-connected Layer)。示例代码# -*- coding:utf-8 -*-# /usr/bin/p..._cnn_class

用栈来判断括号匹配问题_6-4 括号匹配 分数 15 作者 张瑞霞 单位 桂林电子科技大学 本题要求通过栈来判断-程序员宅基地

文章浏览阅读2.9k次,点赞4次,收藏21次。用栈实现:输入一行符号,以#结束,判断其中的括号是否匹配。括号包括:{ } 、 [ ] 、 ( )、 < >如果匹配,输出 right如果不匹配,给出错误提示。包括: (1)对称符号都匹配,输出 “ right “ (2)处理到某个符号时不匹配了,输出 " The $ character '*' is wrong." ,其中$是出错符号的序号,*是出错..._6-4 括号匹配 分数 15 作者 张瑞霞 单位 桂林电子科技大学 本题要求通过栈来判断

【浙大版《Python 程序设计》题目集(解)】第4章-28 矩阵转置(10分)_3*3转置矩阵行列转置python-程序员宅基地

该题目是要求将一个3×3矩阵进行转置,即行和列互换。输入格式为一行9个小于100的整数,输出格式为3行3列的二维数组,每个数据输出占4列。例子给出了一个输入样例和输出样例。使用for循环和格式化输出即可实现。

【机器学习】基于卷积神经网络 CNN 的猫狗分类问题_cnn猫狗分类-程序员宅基地

文章浏览阅读1.2w次,点赞3次,收藏65次。卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),是深度学习(deep learning)的代表算法之一。顾名思义,就是将卷积与前馈神经网络结合,所衍生出来的一种深度学习算法。卷积神经网络CNN的结构图使用卷积神经网络(CNN)实现猫狗分类是一种有效的方法,它能够自动从图像中学习特征并进行分类,提高准确性。_cnn猫狗分类

tf.compat.v1.placeholder-程序员宅基地

文章浏览阅读2.9k次。Inserts a placeholder for a tensor that will be always fed.tf.compat.v1.placeholder( dtype, shape=None, name=None)Important: This tensor will produce an error if evaluated. Its valu...

随便推点

在python中利用shp文件裁剪数据_python利用shp裁剪影像-程序员宅基地

文章浏览阅读2k次,点赞5次,收藏5次。python利用shp文件裁剪图像_python利用shp裁剪影像

亚信科技笔试题_亚信科技笔试题目-程序员宅基地

文章浏览阅读522次。例如:给定 N=6 以及 A[0]=20 A[1]=10 A[2]=30 A[3]=30 A[4]=40 A[5]=10 此函数应返回 10 或者 30。如果字符串A和字符串B含有相同的字母,但是顺序可能不一样,那么A被称为是B的anagram。对于数组中下标为 k 的元素,A[k] 包含了列表中下一个节点的下标,或者是 -1 ——表示列表到此结束,没有下一个节点了。从给出的非空整数数组 A(下标从 0 开始算)中,找到那个藏在其中的列表的长度,将长度值返回。5. Sleep()和wait()的区别?..._亚信科技笔试题目

java/php/net/python家庭安防系统【2024年毕设】-程序员宅基地

文章浏览阅读757次。系统架构图属于系统设计阶段,系统架构图只是这个阶段一个产物,系统的总体架构决定了整个系统的模式,是系统的基础。springboot基于android的天干地支文化科普和动画系统。springboot基于微信小程序的企业资源预约系统的设计与实现。springboot基于springboot的母婴服务管理系统。springboot基于Android的天气预报及推荐系统。springboot基于springboot的外卖系统。ssm基于SSM的儿童疫苗接种管理系统的设计与实现。

CLSR:Contrastive Learning Based Graph Convolution Network forSocial Recommendation_cross-view temporal graph contrastive learning for-程序员宅基地

文章浏览阅读686次,点赞20次,收藏11次。当前的社交推荐模型通常关注建模多图结构,并从这些多图中汇集信息以学习用户的偏好。然而,这些方法往往使用复杂的模型和冗余的参数来获得轻微的性能改进。因此,本文提出了一种集成社交图和交互图信息的对比学习方法,通过对用户嵌入进行融合来获取更精细的用户表示。同时,引入了对比学习框架,通过数据增强构建对比学习的正负样本。总体而言,这篇论文介绍了一种基于对比学习的图卷积网络方法,用于改进社交推荐系统的性能。通过融合社交图和交互图信息,并利用对比学习框架进行数据增强,该方法能够更好地学习用户的偏好。_cross-view temporal graph contrastive learning for session-based recommendat

Quartus使用步骤及联合Modelsim仿真教程_quartus仿真-程序员宅基地

文章浏览阅读5.7k次,点赞19次,收藏69次。Quartus使用记录及与modelsim联合仿真_quartus仿真

编写函数判断闰年_本关任务:编写一个函数,能判断年份n是否是闰年,并调用该函数判断输入的年份是否为-程序员宅基地

文章浏览阅读3.6k次,点赞2次,收藏3次。编写函数 int fun(int n)判断n是否是闰年,定义main函数输入年份,调用fun函数判断,在main函数中输出yes或no。请只提供子函数的编写。【样例输入】2000【样例输出】yesint fun(int n)_本关任务:编写一个函数,能判断年份n是否是闰年,并调用该函数判断输入的年份是否为