直接内存访问--DMA

DMA数据传输:

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

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

在第一种情况下,调用的步骤可以概括如下(以read为例):

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

(2)硬件将数据写入 DMA 缓冲区并在完成时产生一个中断。

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

第二种情形是在 DMA 被异步使用时发生的。以数据采集设备为例:

(1)硬件发出中断来通知新的数据已经到达。

(2)中断处理程序分配一个DMA缓冲区。

(3)外围设备将数据写入缓冲区,然后在完成时发出另一个中断。

(4)处理程序利用DMA分发新的数据,唤醒任何相关进程。

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

分配DMA缓冲区:

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

2.实际操作中,一些设备和一些系统中的高端内存不能用于DMA,这是因为外围设备不能使用高端内存地址。应使用GFP_DMA标志调用kmalloc或者get_free_pages从DMA区间分配内存。

3.使用DMA的设备驱动程序将于连接到总线接口上的硬件通信,这些硬件通常使用的是总线地址。

unsigned long virt_to_bus(volatile void *address);

void *bus_to_virt(unsigned long address);

这些函数在虚拟地址和总线地址见做了简单的转化,但对于必须使用I/O内存管理单元或者必须使用回弹缓冲区的情况下,将不能工作。

通用DMA层:

内核提供了一个与总线--体系架构无关的DMA层,它会隐藏大多数问题,为DMA编写驱动时,使用该层。

使用些列函数的驱动程序都要包含头文件

1.int  dma_set_mask (struct  device  *dev , u64  mask);用于判断给定寻址范围受限的设备在当前主机上是否具备DMA操作的能力。

   如果设备支持常见的32位DMA操作,则没有必要调用dma_set_mask。

2.DMA映射是分配一个DMA缓冲区和为该缓冲区生成设备可访问地址的操作的组合。

   DMA映射建立了一个新的结构类型--dma_addr_t来表示总线地址,dma_addr_t类型的变量对驱动是不透明的;如果cpu直接使用了该结构,将会导致发生不可   

    预期的问题。

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

    一致性DMA映射:必须可以同时被cpu和外围设备访问。必须保存在一致性缓存中。

    流式DMA映射:通常为单独的操作建立流式映射,开发者建议尽量使用流式映射。

    2.1 内核中提供了一下函数用于分配一个DMA一致性的内存区域:

    void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);

    这个函数的返回值为申请到的DMA缓冲区的虚拟地址。此外,该函数还通过参数handle返回DMA缓冲区的总线地址。与之对应的释放函数为:

    void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t handle);

    此外,Linux内核还提供了PCI设备申请DMA缓冲区的函数pci_alloc_consistent(),原型为:

    void *pci_alloc_consistent(struct pci_dev *dev, size_t size, dma_addr_t *dma_addrp);  对应的释放函数为:

    void pci_free_consistent(struct pci_dev *pdev, size_t size, void *cpu_addr, dma_addr_t dma_addr);

 

    2.2DMA池

    DMA池是一个生成小型、一致性DMA映射的机制。调用dma_alloc_coherent获得的映射,可能其最小大小为单个页。如果设备需要的DMA缓冲

    区比这还小,就要使用DMA池了。

    struct dma_pool *dma_pool_create(const char *name,struct device *dev,size_t size,size_t align,size_t allocation);

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

    void dma_pool_destroy(struct dma_pool *pool);

     2.3建立流式DMA映射

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

     DMA_TO_DEVICE

     DMA_FROM_DEVICE

     DMA_BIDIRECTIONAL

     DMA_NONE

     2.3.2当只有一个缓冲区需要被传输时:

     dma_addr_t dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction);

     返回值时总线地址,可以把返回值传递给设备。

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

     size和direction参数必须与映射缓冲区的参数相匹配。

     2.3.3流式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);

     2.4单页流式映射

     2.5分散/聚集映射

3.  ISA设备的DMA

     3.1

     ISA总线语序两种DMA传输:本地DMA和ISA总线控制DMA.这里主要说一下本地DMA

     有三种实体涉及到ISA总线上的DMA数据传输:

     DMA控制器:DMAC

     外围设备

     设备驱动程序

     在pc中,早期DMA控制器能够管理四个"通道",每个通道与一套DMA寄存器相关联.

     注册DMA:

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

     channel是0到7的整数,其中4不可用,通道4在内部用来将从属控制器级联到主控制器上。返回0表示成功。cascade入口是一个占位符,表示4通道不可用

     void free_dma(unsigned int channel);

     在open操作时请求通道比在模块初始化函数中请求通道更好一些。同时还建议在请求中断信号线以后请求DMA通道,在释放中断前释放DMA通道。

     3.2与DMA控制器通信

     DMA通道时一个可共享的资源,在配置通道的时候需要加锁。

     unsigned long claim_dma_lock()

     void release_dma_lock(unsigned lon;g flags);

     配置DMA通道的信息包含三部分:RAM地址,被传输的原子项个数(以字节为单位),传输的方向

     void set_dma_mode(unsigned int channel,char mode);

     void set_dma_addr(unsigned int channel,unsigned int addr);

     void set_dma_count(unsigned int channel,unsigned int count);

     除此之外,当处理DMA设备时,还有许多用于管理设备的函数:

     void disable_dma(unsigned int channel);

     void enable_dma(unsigned int channel);

     int get_dma_residue(unsigned int channel);     该函数返回还未传输的字节数,用于查看DMA传输是否已经结束。

     void clear_dma_ff                                                   

     程序员必须在访问DMA寄存器前清除触发器,触发器用于控制对16位寄存器的访问,我们可以通过两个连续的8位操作访问该寄存器

     当清零时被用来选择低字节,置位选择高字节。

    

 

你可能感兴趣的:(直接内存访问--DMA)