当前位置:首页 > 嵌入式 > 嵌入式教程
[导读]linux dma cache

说到DMA,就会想到Cache,两者本身似乎是好不相关的事物。的确,假设DMA针对内存的目的地址和Cache缓存的对象没有重叠区域,DMA和Cache之间就相安无事,但是,如果有重叠呢,经过DMA操作,Cache缓存对应的内存的数据已经被修改,而CPU本身并不知道,它仍然认为Cache中的数据仍然还是内存中的数据,以后访问Cache映射的内存时,它仍然使用陈旧的Cache数据,这就会发生Cache与内存之间数据"不一致性"的错误。一旦出现这样的情况,没有处理好,驱动就将无法正常运行。那么怎样解决呢?最简单的方法是直接禁止DMA目标地址范围内内存的Cache功能,当然这是牺牲性能的,但却高可靠。不是吗,所以这两者之间究竟怎么平衡,还真不好解决。  其实啊,Cache不一致的情况在其他地方也是可能发生的,比如,对于带MMU功能的arm处理器,在开启MMU之前,需要设置Cache无效,TLB也是如此。

说了,那么多DMA理论的东西,剩下的来点Linux下DMA编程的东西,当然,这里也是点一下,具体怎么操作,我可要卖个关子到下节了。 在内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持scatter/gatherCSG,分散/聚集操作的情况下,DMA缓冲区必须是物理上联系的。

对于ISA设备而言,其DMA操作只能在16MB以下的内存进行,因此,在使用kmalloc()和__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标志,这样能保证获得的内存是具备DMA能力的。内核中定义了__get_free_pages()针对DMA的"快捷方式"__get_dma_pages(),它在申请标志中添加了GFP_DMA,如下所示:

#define __get_dma__pages(gfp_mask, order) 

__get_free_pages((gfp_mask) | GFP_DMA, (order))

"我不想使用order为参数的申请DMA内存,感觉就是怪怪的,那咋办?"

那?这样吧,你就用另外一个函数dma_mem_alloc()源代码如下:

/* dma_mem_alloc()返回值为虚拟地址 */

static unsigned long dma_mem_alloc(int size)

{

int order = get_order(size);//大小->指数

return __get_dma_pages(GFP_KERNEL, order);

}

上节我们说到了dma_mem_alloc()函数,需要说明的是DMA的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU角度上看到的未经转换的内存地址(经过转换的那叫虚拟地址)。在PC上,对于ISA和PCI而言,总线即为物理地址,但并非每个平台都是如此。由于有时候接口总线是通过桥接电路被连接,桥接电路会将IO地址映射为不同的物理地址。例如,在PRep(PowerPC Reference Platform)系统中,物理地址0在设备端看起来是0X80000000,而0通常又被映射为虚拟地址0xC0000000,所以同一地址就具备了三重身份:物理地址0,总线地址0x80000000及虚拟地址0xC0000000,还有一些系统提供了页面映射机制,它能将任意的页面映射为连续的外设总线地址。内核提供了如下函数用于进行简单的虚拟地址/总线地址转换:

unsigned long virt_to_bus(volatile void *address);

void *bus_to_virt(unsigned long address);

在使用IOMMU或反弹缓冲区的情况下,上述函数一般不会正常工作。而且,这两个函数并不建议使用。

需要说明的是设备不一定能在所有的内存地址上执行DMA操作,在这种情况下应该通过下列函数执行DMA地址掩码:

int dma_set_mask(struct device *dev, u64 mask);

比如,对于只能在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);

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

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

以下函数用于分配一个写合并(writecombinbing)的DMA缓冲区:

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

与之对应的是释放函数: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映射:

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缓冲区后,应该将其所有权还给设备,通过下面的函数:[!--empirenews.page--]

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;   //缓冲区长度

}

执行dma_map_sg()后,通过sg_dma_address()后可返回scatterlist对应缓冲区的总线结构,sg_dma_len()可返回scatterlist对应的缓冲区的长度,这两个函数的原型是:

dma_addr_t sg_dma_address(struct scatterlist *sg);   unsigned int sg_dma_len(struct scatterlist *sg);

在DMA传输结束后,可通过dma_map_sg()的反函数dma_unmap_sg()去除DMA映射:

void dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction);   SG映射属于流式DMA映射,与单一缓冲区情况下流式DMA映射类似,如果设备驱动一定要访问映射情况下的SG缓冲区,应该先调用如下函数:

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

访问完后,通过下列函数将所有权返回给设备:

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

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

相应的函数都介绍完了:说真的,好费劲啊,我都想放弃了,可为了小王,我继续哈在linux设备驱动中如何操作呢:

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

int request_dma(unsigned int dmanr, const char * device_id);  同样的,设备结构体指针可作为传入device_id的最佳参数。

使用完DMA通道后,应该使用如下函数释放该通道:void free_dma(unsinged int dmanr);

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭