您的位置:首页 > 运维架构 > Linux

Linux内核开发之内存与I/O访问(六)

2012-12-11 17:46 169 查看
“小王,再告诉你一个好消息,今天是咱们设备驱动程序核心基础理论的最后一节课了,战斗就已经到了最后一刻了,开心不”我眉飞色舞的对小王说。“嗯,开心,我挣扎许久了,终于结束了,只是..”小王伤感的说“只是我觉得怎么能一下就没了呢,心里空荡荡的”.“没关系的…”看着小王噘着嘴调皮而又可爱的样子,我也心软了”核心的理论是讲完了,但你不是没动过手吗,还有很多路要走呢..我还舍…”我一把蒙住自己的嘴.嘿嘿,心里咋想咋们都明白,是不…别伤感了,继续咱们上节的东西:上节我们说到了dma_mem_alloc()函数,需要说明的是DMA的硬件使用总线地址而非物理地址,总线地址是从设备角度上看到的内存地址,物理地址是从CPU角度上看到的未经转换的内存地址(经过转换的那叫虚拟地址)。在PC上,对于ISA和PCI而言,总线即为物理地址,但并非每个平台都是如此。由于有时候接口总线是通过桥接电路被连接,桥接电路会将IO地址映射为不同的物理地址。例如,在PRep(PowerPCReferencePlatform)系统中,物理地址0在设备端看起来是0X80000000,而0通常又被映射为虚拟地址0xC0000000,所以同一地址就具备了三重身份:物理地址0,总线地址0x80000000及虚拟地址0xC0000000,还有一些系统提供了页面映射机制,它能将任意的页面映射为连续的外设总线地址。内核提供了如下函数用于进行简单的虚拟地址/总线地址转换:
unsignedlongvirt_to_bus(volatilevoid*address);
void*bus_to_virt(unsignedlongaddress);
在使用IOMMU或反弹缓冲区的情况下,上述函数一般不会正常工作。而且,这两个函数并不建议使用。需要说明的是设备不一定能在所有的内存地址上执行DMA操作,在这种情况下应该通过下列函数执行DMA地址掩码:intdma_set_mask(structdevice*dev,u64mask);比如,对于只能在24位地址上执行DMA操作的设备而言,就应该调用dma_set_mask(dev,0xffffffff).DMA映射包括两个方面的工作:分配一片DMA缓冲区;为这片缓冲区产生设备可访问的地址。结合前面所讲的,DMA映射必须考虑Cache一致性问题。内核中提供了一下函数用于分配一个DMA一致性的内存区域:
void*dma_alloc_coherent(structdevice*dev,size_tsize,dma_addr_t*handle,gfp_tgfp);
这个函数的返回值为申请到的DMA缓冲区的虚拟地址。此外,该函数还通过参数handle返回DMA缓冲区的总线地址。与之对应的释放函数为:
voiddma_free_coherent(structdevice*dev,size_tsize,void*cpu_addr,dma_addr_thandle);
以下函数用于分配一个写合并(writecombinbing)的DMA缓冲区:
void*dma_alloc_writecombine(structdevice*dev,size_tsize,dma_addr_t*handle,gfp_tgfp);
与之对应的是释放函数:dma_free_writecombine(),它其实就是dma_free_conherent,只不过是用了#define重命名而已。此外,Linux内核还提供了PCI设备申请DMA缓冲区的函数pci_alloc_consistent(),原型为:
void*pci_alloc_consistent(structpci_dev*dev,size_tsize,dma_addr_t*dma_addrp);对应的释放函数为:
voidpci_free_consistent(structpci_dev*pdev,size_tsize,void*cpu_addr,dma_addr_tdma_addr);
相对于一致性DMA映射而言,流式DMA映射的接口较为复杂。对于单个已经分配的缓冲区而言,使用dma_map_single()可实现流式DMA映射:
dma_addr_tdma_map_single(structdevice*dev,void*buffer,size_tsize,enumdma_data_directiondirection);如果映射成功,返回的是总线地址,否则返回NULL.最后一个参数DMA的方向,可能取DMA_TO_DEVICE,DMA_FORM_DEVICE,DMA_BIDIRECTIONAL和DMA_NONE;
与之对应的反函数是:
voiddma_unmap_single(structdevice*dev,dma_addr_t*dma_addrp,size_tsize,enumdma_data_directiondirection);
通常情况下,设备驱动不应该访问unmap()的流式DMA缓冲区,如果你说我就愿意这么做,我又说写什么呢,选择了权利,就选择了责任,对吧。这时可先使用如下函数获得DMA缓冲区的拥有权:
voiddma_sync_single_for_cpu(structdevice*dev,dma_handle_tbus_addr,size_tsize,enumdma_data_directiondirection);
在驱动访问完DMA缓冲区后,应该将其所有权还给设备,通过下面的函数:
voiddma_sync_single_for_device(structdevice*dev,dma_handle_tbus_addr,size_tsize,enumdma_data_directiondirection);如果设备要求较大的DMA缓冲区,在其支持SG模式的情况下,申请多个不连续的,相对较小的DMA缓冲区通常是防止申请太大的连续物理空间的方法,在Linux内核中,使用如下函数映射SG:
intdma_map_sg(structdevice*dev,structscatterlist*sg,intnents,enumdma_data_directiondirection);其中nents是散列表入口的数量,该函数的返回值是DMA缓冲区的数量,可能小于nents。对于scatterlist中的每个项目,dma_map_sg()为设备产生恰当的总线地址,它会合并物理上临近的内存区域。下面在给出scatterlist结构:
structscatterlist
{
structpage*page;
unsignedintoffset;//偏移量
dma_addr_tdma_address;//总线地址
unsignedintlength;//缓冲区长度
}
执行dma_map_sg()后,通过sg_dma_address()后可返回scatterlist对应缓冲区的总线结构,sg_dma_len()可返回scatterlist对应的缓冲区的长度,这两个函数的原型是:
dma_addr_tsg_dma_address(structscatterlist*sg);unsignedintsg_dma_len(structscatterlist*sg);
在DMA传输结束后,可通过dma_map_sg()的反函数dma_unmap_sg()去除DMA映射:voiddma_map_sg(structdevice*dev,structscatterlist*sg,intnents,enumdma_data_directiondirection);SG映射属于流式DMA映射,与单一缓冲区情况下流式DMA映射类似,如果设备驱动一定要访问映射情况下的SG缓冲区,应该先调用如下函数:intdma_sync_sg_for_cpu(structdevice*dev,structscatterlist*sg,intnents,enumdma_data_directiondirection);访问完后,通过下列函数将所有权返回给设备:intdma_map_device(structdevice*dev,structscatterlist*sg,intnents,enumdma_data_directiondirection);Linux系统中可以有一个相对简单的方法预先分配缓冲区,那就是同步“mem=”参数预留内存。例如,对于内存为64MB的系统,通过给其传递mem=62MB命令行参数可以使得顶部的2MB内存被预留出来作为IO内存使用,这2MB内存可以被静态映射,也可以执行ioremap().相应的函数都介绍完了:说真的,好费劲啊,我都想放弃了,可为了小王,我继续哈..在linux设备驱动中如何操作呢:像使用中断一样,在使用DMA之前,设备驱动程序需要首先向系统申请DMA通道,申请DMA通道的函数如下:intrequest_dma(unsignedintdmanr,constchar*device_id);同样的,设备结构体指针可作为传入device_id的最佳参数。使用完DMA通道后,应该使用如下函数释放该通道:voidfree_dma(unsingedintdmanr);
作为本篇的最后,也作为Linux设备驱动核心理论的结束篇,小王,我给你总结一下在Linux设备驱动中DMA相关代码的流程。如下所示:

“小王,我要讲的讲完了,我轻松了,你可要忙碌了,把这一个月来的知识点都好好串串,为了你,我随叫随到..”我说“课余,我也准备一些后面实际的操作内容,帮你串串”我发现自己越来越喜欢小王了..
"嗯,我会的,放心吧.."那美丽的而迷人的微笑眼神又出现了..

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: