DMA编程

一、

DMA基础知识:

  • DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以是系统CPU从实际的IO数据传输过程中摆脱出来,从而大大提供系统的吞吐率。
  • DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后续处理。
  • DMA与cache的平衡:按道理DMA与cache并无关联,但是当DMA针对内存的目的地址和Cache缓存的对象具有重叠区域的时候,两者就存在一定的问题。
  • 如果有重叠,经过DMA操作之后,cache缓存对应的内存的数据已经被需改,而cpu并不知道,它仍然认为cache中的数据还是内存中的数据。
  • 而在之后的访问cache映射的内存时,依然使用旧的cache数据,就会造成cache与内存之间数据不一致性,从而导致驱动无法正常运行。
    HOW?
    如何解决这个问题呢,一般来说会选择直接禁止DMA目标地址范围内内存的cache功能

二、

Linux下DMA编程:
在内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持scatter/gatherCSG,分散/聚集操作的情况下,DMA缓冲区必须是物理上联系的。
对于ISA设备而言,其DMA操作只能在16MB以下的内存进行,因此,在使用kmalloc()__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存是具备DMA能力的。
Linux内核提供了简单的虚拟地址/总线地址的转换:

 unsigned long virt_to_bus(volatile void *address);
    void *bus_to_virt(unsigned long address);   


但是这两个函数通常不建议使用,并且不是所有设备都能在所有的内存地址上执行DMA操作,因此通常会采用下列函数执行DMA地址掩码:

int dma_set_mask(struct device *dev, u64 mask);
//eg:对于只能在24位地址上执行DMA操作的设备而言,就应该调用dma_set_mask(dev, 0xffffffff)

三、

DMA映射包括两个方面的工作:

  • 分配一片DMA缓冲区;
  • 为这片缓冲区产生设备可访问的地址。

结合前面所讲的,DMA映射必须考虑Cache一致性问题。而内核中提供了函数用于分配一个DMA一致性的内存区域:

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


这个函数的返回值为申请到的DMA缓冲区的虚拟地址。此外,该函数还通过参数handle返回DMA缓冲区的总线地址。
以下函数用于分配一个写合并(writecombinbing)的DMA缓冲区:

void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp);
void dma_free_writecombine();//它其实就是dma_free_conherent,只不过是用了#define重命名而已。
//此外,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);


相对于一致性DMA映射而言,流式DMA映射的接口较为复杂。对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射:

void dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);  
//如果映射成功,返回的是总线地址,否则返回NULL。最后一个参数DMA的方向,可能取DMA_TO_DEVICE, DMA_FORM_DEVICE, DMA_BIDIRECTIONAL和DMA_NONE;
void dma_unmap_single(struct device *dev,dma_addr_t *dma_addrp,size_t size,enum dma_data_direction direction);

:
通常情况下,设备驱动不应该访问unmap()的流式DMA缓冲区,如果非要这么做,可先使用如下函数获得DMA缓冲区的拥有权:

 void dma_sync_single_for_cpu(struct device *dev,dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);
 //在驱动访问完DMA缓冲区后,应该将其所有权还给设备,通过下面的函数:
 void dma_sync_single_for_device(struct device *dev,dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);

:
如果设备要求较大的DMA缓冲区,在其支持SG模式的情况下,申请多个不连续的,相对较小的DMA缓冲区通常是防止申请太大的连续物理空间的方法,在Linux内核中,使用如下函数映射SG:

 int dma_map_sg(struct device *dev,struct scatterlist *sg, int nents,enum dma_data_direction direction); 

:
其中nents是散列表入口的数量,该函数的返回值是DMA缓冲区的数量,可能小于nents。对于scatterlist中的每个项目,dma_map_sg()为设备产生恰当的总线地址,它会合并物理上临近的内存区域。
下面在给出scatterlist结构:

struct scatterlist
{
	struct page *page;    
	unsigned int offset;      //偏移量
	dma_addr_t dma_address;   //总线地址   
	unsigned int length;      //缓冲区长度
}          

:
Linux系统中可以有一个相对简单的方法预先分配缓冲区,那就是同步“mem=”参数预留内存。
eg:对于内存为64MB的系统,通过给其传递mem=62MB命令行参数可以使得顶部的2MB内存被预留出来作为IO内存使用,这2MB内存可以被静态映射,也可以执行ioremap().

四、

在linux设备驱动中如何使用DMA呢,类似于使用中断一样,在使用DMA之前,设备驱动程序需要首先向系统申请DMA通道,申请DMA通道的函数如下:

int request_dma(unsigned int dmanr, const char * device_id);  
//同样的,设备结构体指针可作为传入device_id的最佳参数。
//使用完DMA通道后,应该使用如下函数释放该通道:
void free_dma(unsinged int dmanr);

:
Linux设备驱动中DMA相关代码的流程:

  • request_dma()并初始化DMAC (在设备驱动模块加载或open()函数中进行)
  • 申请DMA缓冲区(在设备驱动模块加载或open()函数中进行)
  • 进行DMA传输(在write() read() ioctl()等功能函数中进行)
  • 若使能了对应中断,进行DMA传输后的中断处理(在中断处理程序中进行)
  • 释放DMA缓冲区 free_dma() (在设备驱动模块卸载或relese()函数中进行 )
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 Linux DMA 编程示例: 1. 首先,需要在设备树中定义 DMA 控制器和 DMA 通道。例如: ``` dma-controller@123456 { compatible = "xyz,dma"; reg = <0x123456 0x100>; #dma-cells = <1>; }; mydevice@0 { compatible = "xyz,mydevice"; reg = <0x0 0x1000>; dma-channels = <&dma-controller 0>; }; ``` 2. 在 Linux 驱动程序中,需要获取 DMA 控制器的物理地址,并映射到虚拟地址空间中。例如: ``` static void __iomem *dma_base; static int mydriver_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); dma_base = devm_ioremap_resource(&pdev->dev, res); ... } ``` 3. 在需要使用 DMA 的地方,需要分配 DMA 缓冲区,并将其映射到虚拟地址空间中。例如: ``` struct dma_chan *chan; struct scatterlist sg; dma_addr_t dma_addr; void *buf; buf = kmalloc(size, GFP_KERNEL); if (!buf) { return -ENOMEM; } chan = dma_request_chan(&pdev->dev, "dma0"); if (!chan) { kfree(buf); return -ENODEV; } sg_init_table(&sg, 1); sg_set_buf(&sg, buf, size); dma_addr = dma_map_sg(chan->device->dev, &sg, 1, DMA_TO_DEVICE); if (dma_mapping_error(chan->device->dev, dma_addr)) { kfree(buf); dma_release_channel(chan); return -ENOMEM; } ``` 4. 最后,使用 DMA 进行数据传输。例如: ``` dma_async_memcpy_to_device(chan, dma_addr, src, size); ``` 以上是一个简单的 Linux DMA 编程示例,具体实现可能因硬件平台和应用场景而有所不同。需要根据实际情况进行调整和修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值