在PCI设备驱动开发过程中,DMA的使用非常广泛。PCI9054提供两个DMA通道,每个通道都支持Scatter/ Gather(聚/散)DMA和Block(块)DMA两种式。这两种模式的区别在于后者要求源端和目的端的数据块都是连续存储的,否则就需要进行多次DMA操作;而前者则可以只用一次DMA操作传输非连续存储的多个数据块,但是主机或者本地处理器需要首先建立描述块,然后设置DMA的方式,并用描述块的地址初始化PCI9054内部的DMA描述指针寄存器(Descriptor Pointer Register),最后设置DMACSR0或者DMACSR1来启动DMA操作。DMA启动之后PCI9054首先从描述块中读出第一个描述块,然后开始DMA传输。
所以描述符表的建立对DMA来说至关重要,下面就DMA开发过程做下小结:
down_read(
¤t->mm->mmap_sem
);
// Map user buffer and lock it in memory
rc =
get_user_pages(
current,
current->mm,
UserVa&PAGE_MASK,
TotalPages,
flag_write,
0,
pdx->DmaInfo[channel].PageList,
NULL
);
// Release mmap semaphore
up_read(
¤t->mm->mmap_sem
);
以上函数大家都非常熟悉了,get_user_pages是用来将用户空间的页(pages)映射(锁)入内存,并得到它们的页结构(struct page)指针。
page_cache_release(
pdx->DmaInfo[channel].PageList[i]
);
用到get_user_pages函数还需page_cache_release来解锁。
BusAddr=pci_map_page(
pdx,
pdx->DmaInfo[channel].PageList[i],
offset,
BlockSize,
pdx->DmaInfo[channel].direction
);
由get_user_pages得到页表后,通过pci_map_page函数得到此页的总线地址(此地址写入DMA Channel 0/1 PCI Address寄存器)。
上面是dma的经典写法,当驱动供用户空间使用时,get_user_pages能很好的工作。但是,当驱动时供内核调用,get_user_pages就会出错,此函数对用户缓冲区有效。这时有一个很好的函数virt_to_page,它可以由内核虚拟地址得到页结构,代替了get_user_pages的使用。
pdx->DmaInfo[channel].PageList[i]=virt_to_page(UserVa);
再调用pci_map_page得到总线地址。
其实还有一个函数大家也会用到,virt_to_bus。是不是这个函数的作用相当于上面两个函数的集成,因为它直接由虚拟地址得到了总线地址。不过,没有去验证它。
阅读(3028) | 评论(0) | 转发(0) |