Linux内核DMA机制#精选.docx
- 文档编号:29947813
- 上传时间:2023-08-03
- 格式:DOCX
- 页数:23
- 大小:25.48KB
Linux内核DMA机制#精选.docx
《Linux内核DMA机制#精选.docx》由会员分享,可在线阅读,更多相关《Linux内核DMA机制#精选.docx(23页珍藏版)》请在冰豆网上搜索。
Linux内核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控制器依赖于平台硬件,这里只对i386的8237DMA控制器做简单的说明,它有两个控制器,8个通道,具体说明如下:
控制器1:
通道0-3,字节操作,端口为00-1F
控制器2:
通道4-7,字操作,端口咪C0-DF
-所有寄存器是8bit,与传输大小无关。
-通道4被用来将控制器1与控制器2级联起来。
-通道0-3是字节操作,地址/计数都是字节的。
-通道5-7是字操作,地址/计数都是以字为单位的。
-传输器对于(0-3通道)必须不超过64K的物理边界,对于5-7必须不超过128K边界。
-对于5-7通道pageregisters不用数据bit0,代表128K页
-对于0-3通道pageregisters使用bit0,表示64K页
DMA传输器限制在低于16M物理内存里。
装入寄存器的地址必须是物理地址,而不是逻辑地址。
对于0-3通道来说地址对寄存器的映射如下:
A23...A16A15...A8A7...A0(物理地址)
|...||...||...|
|...||...||...|
|...||...||...|
P7...P0A7...A0A7...A0
|Page|AddrMSB|AddrLSB|(DMA地址寄存器)
对于5-7通道来说地址对寄存器的映射如下:
A23...A17A16A15...A9A8A7...A1A0(物理地址)
|...|\\...\\\...\\
|...|\\...\\\...\(没用)
|...|\\...\\\...\
P7...P1(0)A7A6...A0A7A6...A0
|Page|AddrMSB|AddrLSB|(DMA地址寄存器)
通道5-7传输以字为单位,地址和计数都必须是以字对齐的。
在include/asm-i386/dma.h中有i386平台的8237DMA控制器的各处寄存器的地址及寄存器的定义,这里只对控制寄存器加以说明:
DMAChannelControl/StatusRegister(DCSRX)
第31位表明是否开始
第30位选定Descriptor和Non-Descriptor模式
第29位判断有无中断
第8位请求处理(RequestPending)
第3位Channel是否运行
第2位当前数据交换是否完成
第1位是否由Descriptor产生中断
第0位是否由总线错误引起中断
DMA通道使用的地址
DMA通道用dma_chan结构数组表示,这个结构在kernel/dma.c中,列出如下:
structdma_chan{
intlock;
constchar*device_id;
};
staticstructdma_chandma_chan_busy[MAX_DMA_CHANNELS]={
[4]={1,"cascade"},
};
如果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控制器使用函数request_dma和free_dma来获取和释放DMA通道的所有权,请求DMA通道应在请求了中断线之后,并且在释放中断线之前释放它。
每一个使用DMA的设备也必须使用中断信号线,否则就无法发出数据传输完成的通知。
这两个函数的声明列出如下(在kernel/dma.c中):
intrequest_dma(unsignedintchannel,constchar*name);
voidfree_dma(unsignedintchannel);
DMA控制器被dma_spin_lock的自旋锁所保护。
使用函数claim_dma_lock和release_dma_lock对获得和释放自旋锁。
这两个函数的声明列出如下(在kernel/dma.c中):
unsignedlongclaim_dma_lock();获取DMA自旋锁,该函数会阻塞本地处理器上的中断,因此,其返回值是"标志"值,在重新打开中断时必须使用该值。
voidrelease_dma_lock(unsignedlongflags);释放DMA自旋锁,并且恢复以前的中断状态。
DMA控制器的控制设置信息由RAM地址、传输的数据(以字节或字为单位),以及传输的方向三部分组成。
下面是i386平台的8237DMA控制器的操作函数说明(在include/asm-i386/dma.h中),使用这些函数设置DMA控制器时,应该持有自旋锁。
但在驱动程序做I/O操作时,不能持有自旋锁。
voidset_dma_mode(unsignedintchannel,charmode);该函数指出通道从设备读(DMA_MODE_WRITE)或写(DMA_MODE_READ)数据方式,当mode设置为DMA_MODE_CASCADE时,表示释放对总线的控制。
voidset_dma_addr(unsignedintchannel,unsignedintaddr);函数给DMA缓冲区的地址赋值。
该函数将addr的最低24位存储到控制器中。
参数addr是总线地址。
voidset_dma_count(unsignedintchannel,unsignedintcount);该函数对传输的字节数赋值。
参数count也代表16位通道的字节数,在此情况下,这个数字必须是偶数。
除了这些操作函数外,还有些对DMA状态进行控制的工具函数:
voiddisable_dma(unsignedintchannel);该函数设置禁止使用DMA通道。
这应该在配置DMA控制器之前设置。
voidenable_dma(unsignedintchannel);在DMA通道中包含了合法的数据时,该函数激活DMA控制器。
intget_dma_residue(unsignedintchannel);该函数查询一个DMA传输还有多少字节还没传输完。
函数返回没传完的字节数。
当传输成功时,函数返回值是0。
voidclear_dma_ff(unsignedintchannel)该函数清除DMA触发器(flip-flop),该触发器用来控制对16位寄存器的访问。
可以通过两个连续的8位操作来访问这些寄存器,触发器被清除时用来选择低字节,触发器被置位时用来选择高字节。
在传输8位后,触发器会自动反转;在访问DMA寄存器之前,程序员必须清除触发器(将它设置为某个已知状态)。
DMA映射
一个DMA映射就是分配一个DMA缓冲区并为该缓冲区生成一个能够被设备访问的地址的组合操作。
一般情况下,简单地调用函数virt_to_bus就设备总线上的地址,但有些硬件映射寄存器也被设置在总线硬件中。
映射寄存器(mappingregister)是一个类似于外围设备的虚拟内存等价物。
在使用这些寄存器的系统上,外围设备有一个相对较小的、专用的地址区段,可以在此区段执行DMA。
通过映射寄存器,这些地址被重映射到系统RAM。
映射寄存器具有一些好的特性,包括使分散的页面在设备地址空间看起来是连续的。
但不是所有的体系结构都有映射寄存器,特别地,PC平台没有映射寄存器。
在某些情况下,为设备设置有用的地址也意味着需要构造一个反弹(bounce)缓冲区。
例如,当驱动程序试图在一个不能被外围设备访问的地址(一个高端内存地址)上执行DMA时,反弹缓冲区被创建。
然后,按照需要,数据被复制到反弹缓冲区,或者从反弹缓冲区复制。
根据DMA缓冲区期望保留的时间长短,PCI代码区分两种类型的DMA映射:
∙一致DMA映射它们存在于驱动程序的生命周期内。
一个被一致映射的缓冲区必须同时可被CPU和外围设备访问,这个缓冲区被处理器写时,可立即被设备读取而没有cache效应,反之亦然,使用函数pci_alloc_consistent建立一致映射。
∙流式DMA映射流式DMA映射是为单个操作进行的设置。
它映射处理器虚拟空间的一块地址,以致它能被设备访问。
应尽可能使用流式映射,而不是一致映射。
这是因为在支持一致映射的系统上,每个DMA映射会使用总线上一个或多个映射寄存器。
具有较长生命周期的一致映射,会独占这些寄存器很长时间――即使它们没有被使用。
使用函数dma_map_single建立流式映射。
(1)建立一致DMA映射
函数pci_alloc_consistent处理缓冲区的分配和映射,函数分析如下(在include/asm-generic/pci-dma-compat.h中):
staticinlinevoid*pci_alloc_consistent(structpci_dev*hwdev,
size_tsize,dma_addr_t*dma_handle)
{
returndma_alloc_coherent(hwdev==NULL?
NULL:
&hwdev->dev,
size,dma_handle,GFP_ATOMIC);
}
结构dma_coherent_mem定义了DMA一致性映射的内存的地址、大小和标识等。
结构dma_coherent_mem列出如下(在arch/i386/kernel/pci-dma.c中):
structdma_coherent_mem{
void*virt_base;
u32device_base;
intsize;
intflags;
unsignedlong*bitmap;
};
函数dma_alloc_coherent分配size字节的区域的一致内存,得到的dma_handle是指向分配的区域的地址指针,这个地址作为区域的物理基地址。
dma_handle是与总线一样的位宽的无符号整数。
函数dma_alloc_coherent分析如下(在arch/i386/kernel/pci-dma.c中):
void*dma_alloc_coherent(structdevice*dev,size_tsize,
dma_addr_t*dma_handle,intgfp)
{
void*ret;
//若是设备,得到设备的dma内存区域,即mem=dev->dma_mem
structdma_coherent_mem*mem=dev?
dev->dma_mem:
NULL;
intorder=get_order(size);//将size转换成order,即
//忽略特定的区域,因而忽略这两个标识
gfp&=~(__GFP_DMA|__GFP_HIGHMEM);
if(mem){//设备的DMA映射,mem=dev->dma_mem
//找到mem对应的页
intpage=bitmap_find_free_region(mem->bitmap,mem->size,
order);
if(page>=0){
*dma_handle=mem->device_base+(page< ret=mem->virt_base+(page< memset(ret,0,size); returnret; } if(mem->flags&DMA_MEMORY_EXCLUSIVE) returnNULL; } //不是设备的DMA映射 if(dev==NULL||(dev->coherent_dma_mask<0xffffffff)) gfp|=GFP_DMA; //分配空闲页 ret=(void*)__get_free_pages(gfp,order); if(ret! =NULL){ memset(ret,0,size);//清0 *dma_handle=virt_to_phys(ret);//得到物理地址 } returnret; } 当不再需要缓冲区时(通常在模块卸载时),应该调用函数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中): staticinlinedma_addr_t pci_map_single(structpci_dev*hwdev, void*ptr,size_tsize,intdirection) { returndma_map_single(hwdev==NULL? NULL: &hwdev->dev,ptr,size, (enumma_data_direction)direction); } 函数dma_map_single映射一块处理器虚拟内存,这块虚拟内存能被设备访问,返回内存的物理地址,函数dma_map_single分析如下(在include/asm-i386/dma-mapping.h中): staticinlinedma_addr_tdma_map_single(structdevice*dev,void*ptr, size_tsize,enumdma_data_directiondirection) { BUG_ON(direction==DMA_NONE); //可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新 flush_write_buffers(); returnvirt_to_phys(ptr); //虚拟地址转化为物理地址 } (3)分散/集中映射 分散/集中映射是流式DMA映射的一个特例。 它将几个缓冲区集中到一起进行一次映射,并在一个DMA操作中传送所有数据。 这些分散的缓冲区由分散表结构scatterlist来描述,多个分散的缓冲区的分散表结构组成缓冲区的structscatterlist数组。 分散表结构列出如下(在include/asm-i386/scatterlist.h): structscatterlist{ structpage*page; unsignedintoffset; dma_addr_tdma_address;//用在分散/集中操作中的缓冲区地址 unsignedintlength;//该缓冲区的长度 }; 每一个缓冲区的地址和长度会被存储在structscatterlist项中,但在不同的体系结构中它们在结构中的位置是不同的。 下面的两个宏定义来解决平台移植性问题,这些宏定义应该在一个pci_map_sg被调用后使用: //从该分散表项中返回总线地址 #definesg_dma_address(sg)? t;dma_address) //返回该缓冲区的长度 #definesg_dma_len(sg)? t;length) 函数pci_map_sg完成分散/集中映射,其返回值是要传送的DMA缓冲区数;它可能会小于nents(也就是传入的分散表项的数量),因为可能有的缓冲区地址上是相邻的。 一旦传输完成,分散/集中映射通过调用函数pci_unmap_sg来撤销映射。 函数pci_map_sg分析如下(在include/asm-generic/pci-dma-compat.h中): staticinlineintpci_map_sg(structpci_dev*hwdev,structscatterlist*sg, intnents,intdirection) { returndma_map_sg(hwdev==NULL? NULL: &hwdev->dev,sg,nents, (enumdma_data_direction)direction); } include/asm-i386/dma-mapping.h staticinlineintdma_map_sg(structdevice*dev,structscatterlist*sg, intnents,enumdma_data_directiondirection) { inti; BUG_ON(direction==DMA_NONE); for(i=0;i BUG_ON(! sg[i].page); //将页及页偏移地址转化为物理地址 sg[i].dma_address=page_to_phys(sg[i].page)+sg[i].offset; } //可能有些数据还会保留在处理器的高速缓冲存储器中,因此必须显式刷新 flush_write_buffers(); returnnents; } 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池描述结构,列出如下: structdma_pool{/*thepool*/ structlist_headpage_list;//页链表 spinlock_tlock; size_tblocks_per_page; //每页的块数 size_tsize;//DMA池里的一致内存块的大小 structdevice*dev;//将做DMA的设备 size_tallocation;
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Linux 内核 DMA 机制 精选