Linux 下的DMA浅析

DMA是一种无需CPU的参与就可以让外设和系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。DMA经常与硬件体系结构特别是外设的总线技术密切相关。


一、DMA控制器硬件结构

       DMA允许外围设备和主内存之间直接传输 I/O 数据, DMA 依赖于系统。每一种体系结构DMA传输不同,编程接口也不同。

数据传输可以以两种方式触发:一种软件请求数据,另一种由硬件异步传输

a -- 软件请求数据

     调用的步骤可以概括如下(以read为例):

(1)在进程调用 read 时,驱动程序的方法分配一个 DMA 缓冲区,随后指示硬件传送它的数据。进程进入睡眠。
(2)硬件将数据写入 DMA 缓冲区并在完成时产生一个中断。

(3)中断处理程序获得输入数据,应答中断,最后唤醒进程,该进程现在可以读取数据了。

b -- 由硬件异步传输

      在 DMA 被异步使用时发生的。以数据采集设备为例:

(1)硬件发出中断来通知新的数据已经到达。
(2)中断处理程序分配一个DMA缓冲区。
(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。
(4)处理程序利用DMA分发新的数据,唤醒任何相关进程。

     

      网卡传输也是如此,网卡有一个循环缓冲区(通常叫做 DMA 环形缓冲区)建立在与处理器共享的内存中。每一个输入数据包被放置在环形缓冲区中下一个可用缓冲区,并且发出中断。然后驱动程序将网络数据包传给内核的其它部分处理,并在环形缓冲区中放置一个新的 DMA 缓冲区。

     驱动程序在初始化时分配DMA缓冲区,并使用它们直到停止运行


二、DMA通道使用的地址

        DMA通道用dma_chan结构数组表示,这个结构在kernel/dma.c中,列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct dma_chan {  
  2.     int  lock;  
  3.     const char *device_id;  
  4. };  
  5.    
  6. static struct dma_chan dma_chan_busy[MAX_DMA_CHANNELS] = {  
  7.     [4] = { 1, "cascade" },  
  8. };  

      如果dma_chan_busy[n].lock != 0表示忙,DMA0保留为DRAM更新用,DMA4用作级联。DMA 缓冲区的主要问题是,当它大于一页时,它必须占据物理内存中的连续页。
由于DMA需要连续的内存,因而在引导时分配内存或者为缓冲区保留物理 RAM 的顶部。在引导时给内核传递一个"mem="参数可以保留 RAM 的顶部。例如,如果系统有 32MB 内存,参数"mem=31M"阻止内核使用最顶部的一兆字节。稍后,模块可以使用下面的代码来访问这些保留的内存:

dmabuf = ioremap( 0x1F00000 /* 31M */, 0x100000 /* 1M */);

    分配 DMA 空间的方法,代码调用 kmalloc(GFP_ATOMIC) 直到失败为止,然后它等待内核释放若干页面,接下来再一次进行分配。最终会发现由连续页面组成的DMA 缓冲区的出现。

    一个使用 DMA 的设备驱动程序通常会与连接到接口总线上的硬件通讯,这些硬件使用物理地址,而程序代码使用虚拟地址。基于 DMA 的硬件使用总线地址而不是物理地址,有时,接口总线是通过将 I/O 地址映射到不同物理地址的桥接电路连接的。甚至某些系统有一个页面映射方案,能够使任意页面在外围总线上表现为连续的。

      当驱动程序需要向一个 I/O 设备(例如扩展板或者DMA控制器)发送地址信息时,必须使用 virt_to_bus 转换,在接受到来自连接到总线上硬件的地址信息时,必须使用 bus_to_virt 了。


三、DMA操作函数

        写一个DMA驱动的主要工作包括:DMA通道申请、DMA中断申请、控制寄存器设置、挂入DMA等待队列、清除DMA中断、释放DMA通道

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

      以下具体分析关键函数(linux/arch/arm/mach-s3c2410/dma.c)

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int s3c2410_request_dma(const char *device_id, dmach_t channel,  
  2.     dma_callback_t write_cb, dma_callback_t read_cb) (s3c2410_dma_queue_buffer);  
  3. /* 
  4. 函数描述:申请某通道的DMA资源,填充s3c2410_dma_t 数据结构的内容,申请DMA中断。 
  5. 输入参数:device_id DMA 设备名;channel 通道号; 
  6. write_cb DMA写操作完成的回调函数;read_cb DMA读操作完成的回调函数 
  7. 输出参数:若channel通道已使用,出错返回;否则,返回0 
  8. */  
  9.   
  10. int s3c2410_dma_queue_buffer(dmach_t channel, void *buf_id,  
  11. dma_addr_t data, int size, int write) (s3c2410_dma_stop);  
  12. /* 
  13. 函数描述:这是DMA操作最关键的函数,它完成了一系列动作:分配并初始化一个DMA内核缓冲区控制结构,并将它插入DMA等待队列,设置DMA控制寄存器内容,等待DMA操作触发 
  14. 输入参数: channel 通道号;buf_id,缓冲区标识 
  15. dma_addr_t data DMA数据缓冲区起始物理地址;size DMA数据缓冲区大小;write 是写还是读操作 
  16. 输出参数:操作成功,返回0;否则,返回错误号 
  17. */  
  18.   
  19. int s3c2410_dma_stop(dmach_t channel)  
  20. //函数描述:停止DMA操作。  
  21.   
  22. int s3c2410_dma_flush_all(dmach_t channel)  
  23. //函数描述:释放DMA通道所申请的所有内存资源  
  24.   
  25. void s3c2410_free_dma(dmach_t channel)  
  26. //函数描述:释放DMA通道  


四、DMA映射

       一个DMA映射就是分配一个 DMA 缓冲区并为该缓冲区生成一个能够被设备访问的地址的组合操作。一般情况下,简单地调用函数virt_to_bus 就设备总线上的地址,但有些硬件映射寄存器也被设置在总线硬件中。映射寄存器(mapping register)是一个类似于外围设备的虚拟内存等价物。在使用这些寄存器的系统上,外围设备有一个相对较小的、专用的地址区段,可以在此区段执行 DMA。通过映射寄存器,这些地址被重映射到系统 RAM。映射寄存器具有一些好的特性,包括使分散的页面在设备地址空间看起来是连续的。但不是所有的体系结构都有映射寄存器,特别地,PC 平台没有映射寄存器。

     在某些情况下,为设备设置有用的地址也意味着需要构造一个反弹(bounce)缓冲区。例如,当驱动程序试图在一个不能被外围设备访问的地址(一个高端内存地址)上执行 DMA 时,反弹缓冲区被创建。然后,按照需要,数据被复制到反弹缓冲区,或者从反弹缓冲区复制。

     根据 DMA 缓冲区期望保留的时间长短,PCI 代码区分两种类型的 DMA 映射:

a -- 一致 DMA 映射 

       它们存在于驱动程序的生命周期内。一个被一致映射的缓冲区必须同时可被 CPU 和外围设备访问,这个缓冲区被处理器写时,可立即被设备读取而没有cache效应,反之亦然,使用函数pci_alloc_consistent建立一致映射。

b -- 流式 DMA映射

       流式DMA映射是为单个操作进行的设置。它映射处理器虚拟空间的一块地址,以致它能被设备访问。应尽可能使用流式映射,而不是一致映射。这是因为在支持一致映射的系统上,每个 DMA 映射会使用总线上一个或多个映射寄存器。具有较长生命周期的一致映射,会独占这些寄存器很长时间――即使它们没有被使用。使用函数dma_map_single建立流式映射。


1、建立一致 DMA 映射

      函数pci_alloc_consistent处理缓冲区的分配和映射,函数分析如下(在include/asm-generic/pci-dma-compat.h中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static inline void *pci_alloc_consistent(struct pci_dev *hwdev,   
  2.                  size_t size, dma_addr_t *dma_handle)  
  3. {  
  4.     return dma_alloc_coherent(hwdev == NULL ? NULL : &hwdev->dev,   
  5.                        size, dma_handle, GFP_ATOMIC);  
  6. }  

    结构dma_coherent_mem定义了DMA一致性映射的内存的地址、大小和标识等。结构dma_coherent_mem列出如下(在arch/i386/kernel/pci-dma.c中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct dma_coherent_mem {  
  2.     void        *virt_base;  
  3.     u32     device_base;  
  4.     int     size;  
  5.     int     flags;  
  6.     unsigned long   *bitmap;  
  7. };  

     函数dma_alloc_coherent分配size字节的区域的一致内存,得到的dma_handle是指向分配的区域的地址指针,这个地址作为区域的物理基地址。dma_handle是与总线一样的位宽的无符号整数。 函数dma_alloc_coherent分析如下(在arch/i386/kernel/pci-dma.c中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void *dma_alloc_coherent(struct device *dev, size_t size,  
  2.                dma_addr_t *dma_handle, int gfp)  
  3. {  
  4.     void *ret;  
  5.   //若是设备,得到设备的dma内存区域,即mem= dev->dma_mem  
  6.     struct dma_coherent_mem *mem = dev ? dev->dma_mem : NULL;  
  7.     int order = get_order(size);//将size转换成order,即   
  8.     //忽略特定的区域,因而忽略这两个标识  
  9.     gfp &= ~(__GFP_DMA | __GFP_HIGHMEM);  
  10.    
  11.     if (mem) {//设备的DMA映射,mem= dev->dma_mem  
  12.     //找到mem对应的页  
  13.         int page = bitmap_find_free_region(mem->bitmap, mem->size,  
  14.                              order);  
  15.         if (page >= 0) {  
  16.             *dma_handle = mem->device_base + (page << PAGE_SHIFT);  
  17.             ret = mem->virt_base + (page << PAGE_SHIFT);  
  18.             memset(ret, 0, size);  
  19.             return ret;  
  20.         }  
  21.         if (mem->flags & DMA_MEMORY_EXCLUSIVE)  
  22.             return NULL;  
  23.     }  
  24.    
  25.   //不是设备的DMA映射  
  26.     if (dev == NULL || (dev->coherent_dma_mask < 0xffffffff))  
  27.         gfp |= GFP_DMA;  
  28.   //分配空闲页  
  29.     ret = (void *)__get_free_pages(gfp, order);  
  30.    
  31.     if (ret != NULL) {  
  32.         memset(ret, 0, size);//清0  
  33.         *dma_handle = virt_to_phys(ret);//得到物理地址  
  34.     }  
  35.     return ret;  
  36. }  

当不再需要缓冲区时(通常在模块卸载时),应该调用函数 pci_free_consitent 将它返还给系统。


2、建立流式 DMA 映射

      在流式 DMA 映射的操作中,缓冲区传送方向应匹配于映射时给定的方向值。缓冲区被映射后,它就属于设备而不再属于处理器了。在缓冲区调用函数pci_unmap_single撤销映射之前,驱动程序不应该触及其内容。

     在缓冲区为 DMA 映射时,内核必须确保缓冲区中所有的数据已经被实际写到内存。可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新。在刷新之后,由处理器写入缓冲区的数据对设备来说也许是不可见的。

     如果欲映射的缓冲区位于设备不能访问的内存区段时,某些体系结构仅仅会操作失败,而其它的体系结构会创建一个反弹缓冲区。反弹缓冲区是被设备访问的独立内存区域,反弹缓冲区复制原始缓冲区的内容。

     函数pci_map_single映射单个用于传送的缓冲区,返回值是可以传递给设备的总线地址,如果出错的话就为 NULL。一旦传送完成,应该使用函数pci_unmap_single 删除映射。其中,参数direction为传输的方向,取值如下:

PCI_DMA_TODEVICE 数据被发送到设备。
PCI_DMA_FROMDEVICE如果数据将发送到 CPU。
PCI_DMA_BIDIRECTIONAL数据进行两个方向的移动。
PCI_DMA_NONE 这个符号只是为帮助调试而提供。

     函数pci_map_single分析如下(在arch/i386/kernel/pci-dma.c中)

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static inline dma_addr_t pci_map_single(struct pci_dev *hwdev,   
  2.                 void *ptr, size_t size, int direction)  
  3. {  
  4.     return dma_map_single(hwdev == NULL ? NULL : &hwdev->dev, ptr, size,   
  5.                                       (enum ma_data_direction)direction);  
  6. }  

      函数dma_map_single映射一块处理器虚拟内存,这块虚拟内存能被设备访问,返回内存的物理地址,函数dma_map_single分析如下(在include/asm-i386/dma-mapping.h中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static inline dma_addr_t dma_map_single(struct device *dev, void *ptr,  
  2.                         size_t size, enum dma_data_direction direction)  
  3.   
  4. {  
  5.     BUG_ON(direction == DMA_NONE);  
  6.   //可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新  
  7.     flush_write_buffers();  
  8.     return virt_to_phys(ptr); //虚拟地址转化为物理地址  
  9.   
  10. }  

3、分散/集中映射

      分散/集中映射是流式 DMA 映射的一个特例。它将几个缓冲区集中到一起进行一次映射,并在一个 DMA 操作中传送所有数据。这些分散的缓冲区由分散表结构scatterlist来描述,多个分散的缓冲区的分散表结构组成缓冲区的struct scatterlist数组

      分散表结构列出如下(在include/asm-i386/scatterlist.h):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct scatterlist {  
  2.     struct page     *page;  
  3.     unsigned int    offset;  
  4.     dma_addr_t      dma_address;  //用在分散/集中操作中的缓冲区地址  
  5.     unsigned int    length;//该缓冲区的长度  
  6. };  

    每一个缓冲区的地址和长度会被存储在 struct scatterlist 项中,但在不同的体系结构中它们在结构中的位置是不同的。下面的两个宏定义来解决平台移植性问题,这些宏定义应该在一个pci_map_sg 被调用后使用:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //从该分散表项中返回总线地址  
  2. #define sg_dma_address(sg)  �sg)->dma_address)  
  3. //返回该缓冲区的长度   
  4. #define sg_dma_len(sg)      �sg)->length)  
   函数 pci_map_sg 完成分散/集中映射,其返回值是要传送的 DMA 缓冲区数;它可能会小于 nents(也就是传入的分散表项的数量),因为可能有的缓冲区地址上是相邻的。一旦传输完成,分散/集中映射通过调用函数pci_unmap_sg 来撤销映射。 函数pci_map_sg分析如下(在include/asm-generic/pci-dma-compat.h中):
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static inline int pci_map_sg(struct pci_dev *hwdev, struct scatterlist *sg,  
  2.                                 int nents, int direction)  
  3. {  
  4.     return dma_map_sg(hwdev == NULL ? NULL : &hwdev->dev, sg, nents,   
  5. (enum dma_data_direction)direction);  
  6. }  
  7. include/asm-i386/dma-mapping.h  
  8. static inline int dma_map_sg(struct device *dev, struct scatterlist *sg,   
  9. int nents, enum dma_data_direction direction)  
  10. {  
  11.     int i;  
  12.    
  13.     BUG_ON(direction == DMA_NONE);  
  14.    
  15.     for (i = 0; i < nents; i++ ) {  
  16.         BUG_ON(!sg[i].page);  
  17.     //将页及页偏移地址转化为物理地址  
  18.         sg[i].dma_address = page_to_phys(sg[i].page) + sg[i].offset;  
  19.     }  
  20.     //可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新  
  21.     flush_write_buffers();  
  22.     return nents;  
  23. }  


五、DMA池

      许多驱动程序需要又多又小的一致映射内存区域给DMA描述子或I/O缓存buffer,这使用DMA池比用dma_alloc_coherent分配的一页或多页内存区域好,DMA池用函数dma_pool_create创建,用函数dma_pool_alloc从DMA池中分配一块一致内存,用函数dmp_pool_free放内存回到DMA池中,使用函数dma_pool_destory释放DMA池的资源。

     结构dma_pool是DMA池描述结构,列出如下:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct dma_pool {   /* the pool */  
  2.     struct list_head    page_list;//页链表  
  3.     spinlock_t      lock;  
  4.     size_t          blocks_per_page; //每页的块数  
  5.     size_t          size;     //DMA池里的一致内存块的大小  
  6.     struct device       *dev; //将做DMA的设备  
  7.     size_t          allocation; //分配的没有跨越边界的块数,是size的整数倍  
  8.     char            name [32]; //池的名字  
  9.     wait_queue_head_t   waitq;  //等待队列  
  10.     struct list_head    pools;  
  11. };  

     函数dma_pool_create给DMA创建一个一致内存块池,其参数name是DMA池的名字,用于诊断用,参数dev是将做DMA的设备,参数size是DMA池里的块的大小,参数align是块的对齐要求,是2的幂,参数allocation返回没有跨越边界的块数(或0)。

     函数dma_pool_create返回创建的带有要求字符串的DMA池,若创建失败返回null。对被给的DMA池,函数dma_pool_alloc被用来分配内存,这些内存都是一致DMA映射,可被设备访问,且没有使用缓存刷新机制,因为对齐原因,分配的块的实际尺寸比请求的大。如果分配非0的内存,从函数dma_pool_alloc返回的对象将不跨越size边界(如不跨越4K字节边界)。这对在个体的DMA传输上有地址限制的设备来说是有利的。

  函数dma_pool_create分析如下(在drivers/base/dmapool.c中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct dma_pool *dma_pool_create (const char *name, struct device *dev,  
  2.               size_t size, size_t align, size_t allocation)  
  3. {  
  4.     struct dma_pool     *retval;  
  5.    
  6.     if (align == 0)  
  7.         align = 1;  
  8.     if (size == 0)  
  9.         return NULL;  
  10.     else if (size < align)  
  11.         size = align;  
  12.     else if ((size % align) != 0) {//对齐处理  
  13.         size += align + 1;  
  14.         size &= ~(align - 1);  
  15.     }  
  16.   //如果一致内存块比页大,是分配为一致内存块大小,否则,分配为页大小  
  17.     if (allocation == 0) {  
  18.         if (PAGE_SIZE < size)//页比一致内存块小  
  19.             allocation = size;  
  20.         else  
  21.             allocation = PAGE_SIZE;//页大小  
  22.         // FIXME: round up for less fragmentation  
  23.     } else if (allocation < size)  
  24.         return NULL;  
  25.   //分配dma_pool结构对象空间  
  26.     if (!(retval = kmalloc (sizeof *retval, SLAB_KERNEL)))  
  27.         return retval;  
  28.    
  29.     strlcpy (retval->name, name, sizeof retval->name);  
  30.    
  31.     retval->dev = dev;  
  32.   //初始化dma_pool结构对象retval  
  33.     INIT_LIST_HEAD (&retval->page_list);//初始化页链表  
  34.     spin_lock_init (&retval->lock);  
  35.     retval->size = size;  
  36.     retval->allocation = allocation;  
  37.     retval->blocks_per_page = allocation / size;  
  38.     init_waitqueue_head (&retval->waitq);//初始化等待队列  
  39.    
  40.     if (dev) {//设备存在时  
  41.         down (&pools_lock);  
  42.         if (list_empty (&dev->dma_pools))  
  43.       //给设备创建sysfs文件系统属性文件  
  44.             device_create_file (dev, &dev_attr_pools);  
  45.         /* note:  not currently insisting "name" be unique */  
  46.         list_add (&retval->pools, &dev->dma_pools); //将DMA池加到dev中  
  47.         up (&pools_lock);  
  48.     } else  
  49.         INIT_LIST_HEAD (&retval->pools);  
  50.    
  51.     return retval;  
  52. }  

       函数dma_pool_alloc从DMA池中分配一块一致内存,其参数pool是将产生块的DMA池,参数mem_flags是GFP_*位掩码,参数handle是指向块的DMA地址,函数dma_pool_alloc返回当前没用的块的内核虚拟地址,并通过handle给出它的DMA地址,如果内存块不能被分配,返回null。

      函数dma_pool_alloc包裹了dma_alloc_coherent页分配器,这样小块更容易被总线的主控制器使用。这可能共享slab分配器的内容。

      函数dma_pool_alloc分析如下(在drivers/base/dmapool.c中):

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void *dma_pool_alloc (struct dma_pool *pool, int mem_flags, dma_addr_t *handle)  
  2. {  
  3.     unsigned long       flags;  
  4.     struct dma_page     *page;  
  5.     int         map, block;  
  6.     size_t          offset;  
  7.     void            *retval;  
  8.    
  9. restart:  
  10.     spin_lock_irqsave (&pool->lock, flags);  
  11.     list_for_each_entry(page, &pool->page_list, page_list) {  
  12.         int     i;  
  13.         /* only cachable accesses here ... */  
  14.         //遍历一页的每块,而每块又以32字节递增  
  15.         for (map = 0, i = 0;  
  16.                 i < pool->blocks_per_page; //每页的块数  
  17.                 i += BITS_PER_LONG, map++) { // BITS_PER_LONG定义为32  
  18.             if (page->bitmap [map] == 0)  
  19.                 continue;  
  20.             block = ffz (~ page->bitmap [map]);//找出第一个0  
  21.             if ((i + block) < pool->blocks_per_page) {  
  22.                 clear_bit (block, &page->bitmap [map]);  
  23.        //得到相对于页边界的偏移  
  24.                 offset = (BITS_PER_LONG * map) + block;  
  25.                 offset *= pool->size;  
  26.                 goto ready;  
  27.             }  
  28.         }  
  29.     }  
  30. //给DMA池分配dma_page结构空间,加入到pool->page_list链表,  
  31. //并作DMA一致映射,它包括分配给DMA池一页。  
  32. // SLAB_ATOMIC表示调用 kmalloc(GFP_ATOMIC) 直到失败为止,  
  33. //然后它等待内核释放若干页面,接下来再一次进行分配。  
  34.     if (!(page = pool_alloc_page (pool, SLAB_ATOMIC))) {  
  35.         if (mem_flags & __GFP_WAIT) {  
  36.             DECLARE_WAITQUEUE (wait, current);  
  37.    
  38.             current->state = TASK_INTERRUPTIBLE;  
  39.             add_wait_queue (&pool->waitq, &wait);  
  40.             spin_unlock_irqrestore (&pool->lock, flags);  
  41.    
  42.             schedule_timeout (POOL_TIMEOUT_JIFFIES);  
  43.    
  44.             remove_wait_queue (&pool->waitq, &wait);  
  45.             goto restart;  
  46.         }  
  47.         retval = NULL;  
  48.         goto done;  
  49.     }  
  50.    
  51.     clear_bit (0, &page->bitmap [0]);  
  52.     offset = 0;  
  53. ready:  
  54.     page->in_use++;  
  55.     retval = offset + page->vaddr; //返回虚拟地址  
  56.     *handle = offset + page->dma; //相对DMA地址  
  57. #ifdef  CONFIG_DEBUG_SLAB  
  58.     memset (retval, POOL_POISON_ALLOCATED, pool->size);  
  59. #endif  
  60. done:  
  61.     spin_unlock_irqrestore (&pool->lock, flags);  
  62.     return retval;  
  63. }  

六、一个简单的使用DMA 例子

      示例:下面是一个简单的使用DMA进行传输的驱动程序,它是一个假想的设备,只列出DMA相关的部分来说明驱动程序中如何使用DMA的。

      函数dad_transfer是设置DMA对内存buffer的传输操作函数,它使用流式映射将buffer的虚拟地址转换到物理地址,设置好DMA控制器,然后开始传输数据。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int dad_transfer(struct dad_dev *dev, int write, void *buffer,   
  2.                 size_t count)   
  3. {   
  4.    dma_addr_t bus_addr;   
  5.    unsigned long flags;   
  6.    
  7.    /* Map the buffer for DMA */   
  8.    dev->dma_dir = (write ? PCI_DMA_TODEVICE : PCI_DMA_FROMDEVICE);   
  9.    dev->dma_size = count;  
  10.   //流式映射,将buffer的虚拟地址转化成物理地址  
  11.    bus_addr = pci_map_single(dev->pci_dev, buffer, count,   
  12.                              dev->dma_dir);   
  13.    dev->dma_addr = bus_addr; //DMA传送的buffer物理地址  
  14.    
  15.    //将操作控制写入到DMA控制器寄存器,从而建立起设备   
  16.    writeb(dev->registers.command, DAD_CMD_DISABLEDMA);   
  17.  //设置传输方向--读还是写  
  18.    writeb(dev->registers.command, write ? DAD_CMD_WR : DAD_CMD_RD);    
  19.    writel(dev->registers.addr, cpu_to_le32(bus_addr));//buffer物理地址   
  20.    writel(dev->registers.len, cpu_to_le32(count)); //传输的字节数  
  21.    
  22.    //开始激活DMA进行数据传输操作   
  23.    writeb(dev->registers.command, DAD_CMD_ENABLEDMA);   
  24.    return 0;   
  25. }  

函数dad_interrupt是中断处理函数,当DMA传输完时,调用这个中断函数来取消buffer上的DMA映射,从而让内核程序可以访问这个buffer。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void dad_interrupt(int irq, void *dev_id, struct pt_regs *regs)   
  2. {   
  3.   struct dad_dev *dev = (struct dad_dev *) dev_id;   
  4.   
  5.   /* Make sure it's really our device interrupting */    
  6.   
  7.   /* Unmap the DMA buffer */   
  8.   pci_unmap_single(dev->pci_dev, dev->dma_addr, dev->dma_size,   
  9.        dev->dma_dir);   
  10.   
  11.   /* Only now is it safe to access the buffer, copy to user, etc. */   
  12.   ...   
  13. }  
函数dad_open打开设备,此时应申请中断号及DMA通道
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int dad_open (struct inode *inode, struct file *filp)   
  2. {   
  3.   struct dad_device *my_device;   
  4.   
  5.   // SA_INTERRUPT表示快速中断处理且不支持共享 IRQ 信号线  
  6.   if ( (error = request_irq(my_device.irq, dad_interrupt,   
  7.                             SA_INTERRUPT, "dad", NULL)) )   
  8.       return error; /* or implement blocking open */   
  9.   
  10.   if ( (error = request_dma(my_device.dma, "dad")) ) {   
  11.       free_irq(my_device.irq, NULL);   
  12.       return error; /* or implement blocking open */   
  13.   }   
  14.   
  15.   return 0;   
  16. }  

在与open 相对应的 close 函数中应该释放DMA及中断号。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void dad_close (struct inode *inode, struct file *filp)   
  2. {   
  3.   struct dad_device *my_device;   
  4.   free_dma(my_device.dma);   
  5.   free_irq(my_device.irq, NULL);   
  6.   ……  
  7. }  
函数dad_dma_prepare初始化DMA控制器,设置DMA控制器的寄存器的值,为 DMA 传输作准备。
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int dad_dma_prepare(int channel, int mode, unsigned int buf,   
  2.                   unsigned int count)   
  3. {   
  4.   unsigned long flags;   
  5.   
  6.   flags = claim_dma_lock();   
  7.   disable_dma(channel);   
  8.   clear_dma_ff(channel);   
  9.   set_dma_mode(channel, mode);   
  10.   set_dma_addr(channel, virt_to_bus(buf));   
  11.   set_dma_count(channel, count);   
  12.   enable_dma(channel);   
  13.   release_dma_lock(flags);   
  14.   
  15.   return 0;   
  16. }  

函数dad_dma_isdone用来检查 DMA 传输是否成功结束。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int dad_dma_isdone(int channel)   
  2. {   
  3.    int residue;   
  4.    unsigned long flags = claim_dma_lock ();   
  5.    residue = get_dma_residue(channel);   
  6.    release_dma_lock(flags);   
  7.    return (residue == 0);   
  8. }  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402260.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从值栈中获取数据

使用struts2的标签ognl表达式获取值栈数据 <s:property value"ognl表达式"/> 一、获取字符串 示例 打印后台string变量的值 jsp页面 Java代码 二、获取对象 示例 打印user对象的userName与userPwd的值 jsp页面 java代码 三、获取list集合 Java代码 1.通过list[…

SQL 事务

事务的acid理解简介ACID&#xff0c;是指在可靠数据库管理系统&#xff08;DBMS&#xff09;中&#xff0c;事务(transaction)所应该具有的四个特性&#xff1a;原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isola…

网络协议复习

不同协议所属的层次如下图&#xff1a; IP IP地址一开始是分类编址&#xff0c;到了20世纪90年代更换为无分类编址。分类编址时IP地址共有五类ABCDE。对于ABC类地址&#xff0c;IP地址都可以划分为网络标识和主机标识。从一个IP地址中提取网络地址要用网络掩码和IP地址进行与运…

CSDN-markdown编辑器使用手册

Markdown手册欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也…

Office 365身份认证管理-添加并验证联合认证域

首先需要安装MicrosoftOnlineServicesSign-inAssistant接受许可并安装安装完成接着安装WindowsAzureActiveDirectoryModuleforWindowsPowerShell安装程序启动接受许可设置安装路径开始安装安装完毕登陆并打开office365管理中心&#xff0c;选择添加一个域开始域添加向导输入我们…

codevs1040统计单词个数(区间+划分型dp)

1040 统计单词个数 2001年NOIP全国联赛提高组 时间限制: 1 s空间限制: 128000 KB题目等级 : 黄金 Gold题目描述 Description给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入&#xff0c;且保证每行一定为20个)。要求将此字母串分成k…

翻译词典推荐

前言 今天在看一个API&#xff0c;遇到一些生词不会&#xff0c;花了些时间在选择词典上面&#xff0c;做个总结。 我的经历 先说说我使用词典的经历吧&#xff1a; 无网络条件&#xff1a;首选金山词霸&#xff0c;词霸本身1G多安装包配合将近2G的词典包&#xff0c;即便是断网…

用URLGather来管理和保存你的页面

下载链接&#xff1a;http://url-gather.software.informer.com/download/#downloading安装的过程简单&#xff0c;这里不一一叙述。安装成功后&#xff0c;找到软件安装的路径&#xff0c;如下&#xff1a;进入软件之后&#xff0c;你只要熟悉以下的功能就能轻松的管理你的网站…

Exynos4412 中断处理流程详解

Linux 中&#xff0c;当外设触发中断后&#xff0c;大体处理流程如下&#xff1a; a -- 具体CPU architecture相关的模块会进行现场保护&#xff0c;然后调用machine driver对应的中断处理handler&#xff1b; b -- machine driver对应的中断处理handler中会根据硬件的信息获取…

Exynos4412 中断驱动开发相关问题总结

1、Linux 中如何标识一个外部中断&#xff1f; 在linux kernel中&#xff0c;我们使用下面两个ID来标识一个来自外设的中断&#xff1a; a -- IRQ number CPU需要为每一个外设中断编号&#xff0c;我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID&#xff0c;和硬…

Exynos4412 IIC 总线驱动开发相关问题总结

一 、问题 1、IIC总线上的设备是怎么描述的&#xff1f; struct i2c_client{struct device dev;...};2、IIC总线上的驱动是怎么描述的&#xff1f; struct i2c_driver {struct device_driver driver;};3、IIC总线上的设备和驱动是怎么匹配的 1)、对于 Cortex - A8 通过driver…

sersync+rsync实现实时同步

在分布式应用中会遇到一个问题&#xff0c;就是多个服务器间的文件如何能始终保持一致。一种经典的办法是将需要保持一致的文件存储在NFS上&#xff0c;这种方法虽然简单方便但却将本来多点的应用在文件存储上又变成了单点&#xff0c;这违背了分布式应用部署的初衷。为了保留多…

Exynos4412 IIC总线驱动开发(二)—— IIC 驱动开发

前面在Exynos4412 IIC总线驱动开发&#xff08;一&#xff09;—— IIC 基础概念及驱动架构分析 中学习了IIC驱动的架构&#xff0c;下面进入我们的驱动开发过程 首先看一张代码层次图&#xff0c;有助于我们的理解 上面这些代码的展示是告诉我们&#xff1a;linux内核和芯片提…

Exynos4412 IIC总线驱动开发(一)—— IIC 基础概念及驱动架构分析

关于Exynos4412 IIC 裸机开发请看 &#xff1a;Exynos4412 裸机开发 —— IIC总线 &#xff0c;下面回顾下 IIC 基础概念 一、IIC 基础概念 IIC(Inter&#xff0d;Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线&#xff0c;用于连接微控制器及其外围设备。I…

Python 爬虫进阶一之爬虫框架概述

综述 爬虫入门之后&#xff0c;我们有两条路可以走。 一个是继续深入学习&#xff0c;以及关于设计模式的一些知识&#xff0c;强化 Python 相关知识&#xff0c;自己动手造轮子&#xff0c;继续为自己的爬虫增加分布式&#xff0c;多线程等功能扩展。另一条路便是学习一些优秀…

1039. 到底买不买(20)

1039. 到底买不买&#xff08;20&#xff09; 小红想买些珠子做一串自己喜欢的珠串。卖珠子的摊主有很多串五颜六色的珠串&#xff0c;但是不肯把任何一串拆散了卖。于是小红要你帮忙判断一下&#xff0c;某串珠子里是否包含了全部自己想要的珠子&#xff1f;如果是&#xff0c…

Exynos4412 ADC 设备驱动开发

具体ADC硬件知识及裸机驱动请看&#xff1a; Exynos4412裸机开发 —— A/D转换器 1、原理图如下&#xff1a; 2、相关寄存器信息 ADC_BASE 0x126C0000ADCCON 0x0000 1<<0 | 1<<14 | 0X1<<16 | 0XFF<<6ADCDLY 0x000…

Python 爬虫进阶二之 PySpider 框架安装配置

PySpider官方文档 项目地址 官方文档 安装 phantomjs PhantomJS 是一个基于 WebKit 的服务器端 JavaScript API。它全面支持 web 而不需浏览器支持&#xff0c;其快速、原生支持各种 Web 标准&#xff1a;DOM 处理、CSS 选择器、JSON、Canvas 和 SVG。 PhantomJS 可以用于页…

Exynos4412 中断驱动开发(三)—— 设备树中中断节点的创建

提到中断就必须了解到GIC&#xff0c;下面先了解一下GIC 一、GIC概念 GIC&#xff08;Generic Interrupt Controller&#xff09;是ARM公司提供的一个通用的中断控制器。GIC通过AMBA&#xff08;Advanced Microcontroller Bus Architecture&#xff09;这样的片上总线连接到一个…

Exynos4412 中断驱动开发(二)—— 中断处理流程分析

前面已经学习了中断的注册过程&#xff0c;下面由一张流程图来看一下当中断发生时的处理流程&#xff1a; 中断发生之后处理流程 a -- 具体的CPU architecture相关模块进行现场保护&#xff0c;然后调用machine driver执行对应的中断处理handler; b -- machine driver对应中断处…