具体如何实现DMA呢?
在microsoft windows CE device driver kit中有一节是:Implementing DMA for NDIS Miniport Drivers
专门提到了这个问题,当前ce还没有自己的DMA机制,然而开发人员可以在他们自己的小端口卡驱动程序中实现相同的功能,但是写的太简单了
Implementing DMA for NDIS Miniport Drivers
DMA is important for efficient networking because it enables the layers in
the networking architecture to share data without first copying that data
two categories of DMA: slave DMA and busmaster DMAB
Slave DMA
Slave DMA is appropriate for OEMs because it requires a block of memory that
is pre-allocated in the device memory map of the Windows CE–based platform.
Miniport drivers can map that block of physical memory to the driver’virtual
memory space, and then use the VirtualAlloc and VirtualCopy functions to move
data in and out of that space
busmaster DMA
To receive data, the driver allocates a shared memory block, transfers the
data to buffers within that block, and uses NDIS functions to indicate that a
packet has arrived. To send data, the driver uses the LockPages and
UnlockPages functions to map the virtual memory to device memory, informs the
NIC of the addresses of the data, and instructs the NIC to send the data.
This method can be faster for larger data block sizes.
后来发MAIL给一位曾经遇到此问题的朋友,回复如下:
事实上在使用DMA是的关键是得到一个连续的物理地址空间,然后将该空间的物理地址放入DMA控制器的源地址或者目的地址寄存器中(根据你使用DMA的方向决定);
然后等待DMA中断;
由于DMA使用的是物理地址但是在wince中只能使用虚拟地址,所以在DMA中断产生之后如需分析数据请使用虚拟地址;
特别提示参考一下的文章:
DMA的使用
1、 芯片DMA的使用要点:
AK3224芯片的DMA使用中,RAM的地址作为DMA传输的目标地址、源地址,必须要4字节对齐。而且DMA的操作长度以内的RAM地址,必须连续。
不过在使用中发现:Nandflash驱动中RAM地址作为目标地址时,只需要2字节对齐。RAM地址作为源地址可不需对齐。(其他情况需要逐一验证)
2、 wince中的DMA使用:
根据DMA一次操作的RAM地址必须连续的特性,在驱动DMA使用时,我们需要确保虚拟地址映射的物理地址是连续的。有3个途径:
1:数据区地址是由应用层或者其他进程、线程传入的,驱动并不知道其虚拟地址对应的物理地址是否一直连续。
由于wince的内存申请,是以4K字节为一个页,一段数据的内存申请可能跨越多个页。因此,只要数据区长度大于1字节,就有可能其物理地址是跨越的、不连续的。为了确保DMA操作,我们必须查询这段数据区在RAM上的物理分布。
首先,得到数据区所在的虚拟页: VirPageStart = (ULONG)pSourceBuffer & 0xFFFFF000;
其次,得到数据区在页内的偏移地址 :offset = (ULONG)pSourceBuffer & 0x0FFF;
计算数据区是否跨越页段
if(offset + NumberOfBytes > 4096)
PageSize = WCE_UNIFORM_SIZE - offset; //整个数据跨越此页,则DMA传输需要分多个部分,一次一个页段的传
else
PageSize = NumberOfBytes; //数据区没有跨越页
由得到的页地址,查询映射的物理地址。
if(!LockPages((LPVOID)VirPageStart, 4096, &TransAddr, LOCKFLAG_READ))
{
//异常处理
}
UnlockPages((LPVOID)VirPageStart, 4096);
得到了映射的物理地址TransAddr后,根据RAM是目标地址还是源地址,做进一步的处理。
假设一个数据区作为DMA源地址,大小为9K。在虚拟地址首页的偏移为4K。那么它必然跨越3个页段。
首先查询第一页的物理地址发送,第一个页的2K数据。然后查询第二页的物理地址,发送4K数据。最后查询第三页的物理地址,发送3K数据。
2:数据区的申请可以使用AllocPhysMem函数申请。
LPVOID AllocPhysMem(
DWORD cbSize, 参数1:数据区大小
DWORD fdwProtect, 参数2:保护标记
DWORD dwAlignmentMask, 参数3:0(default system)
DWORD dwFlags, 参数4:0(Reserved for future use)
PULONG pPhysicalAddress 参数5:得到数据区对应的物理地址
);
AllocPhysMem函数返回值为指向申请后的虚拟地址指针。
如:pSerialHead->RxBufferInfo.RxCharBuffer = //alloc physical memory
AllocPhysMem(pSerialHead->RxBufferInfo.Length + 16, PAGE_READWRITE, 0, 0, &RX_PhyAddr);
由于此函数必定申请到一片连续的物理地址,因此pSerialHead->RxBufferInfo.RxCharBuffer的使用不再需要查询是否跨越多个页段。
但是,AllocPhysMem函数申请的物理地址可能会跨越多个RAM CHIP。因此,在使用1片以上RAM芯片的系统中,依然需要查询是否跨越CHIP。
AllocPhysMem函数使用后,需要使用FreePhysMem函数进行释放。
3:数据区可以在系统config.bib文件中,预先定义好一片连续、不跨越CHIP的RAM空间。
如下,系统保留了虚拟地址0x80024000开始,大小为0x3000的一段RAM。
SER_DMA 80024000 00003000 RESERVED
那么驱动DMA使用中,不再需要对这段内存,进行任何的查询动作。我们只需要在进程空间中做映射即可。
pSerialHead->RxBufferInfo.RxCharBuffer = VirtualAlloc(0, RX_PhySize, MEM_RESERVE, PAGE_NOACCESS);
if (pSerialHead->RxBufferInfo.RxCharBuffer == NULL)
{
DEBUGMSG(ZONE_ERROR, (TEXT("COM_Init:: VirtualAlloc failed!\\r\\n")));
return(NULL);
}
else
{
if (!VirtualCopy((PVOID)pSerialHead->RxBufferInfo.RxCharBuffer, (PVOID)(RX_PhyAddr),
RX_PhySize, (PAGE_READWRITE | PAGE_NOCACHE)))
{
DEBUGMSG(ZONE_ERROR, (TEXT("COM_Init:: VirtualCopy failed!\\r\\n")));
return(NULL);
}
}
上面这段程序中,先使用函数VirtualAlloc,在进程空间中申请一段保留的虚拟地址空间。然后使用VirtualCopy,把需要使用的物理地址空间,映射到已经申请好的虚拟地址上。使用完毕,必须使用函数VirtualFree进行释放。
LPVOID VirtualAlloc(
LPVOID lpAddress,
DWORD dwSize,
DWORD flAllocationType,
DWORD flProtect
);
BOOL VirtualCopy(
LPVOID lpvDest,
LPVOID lpvSrc,
DWORD cbSize,
DWORD fdwProtect
);
BOOL VirtualFree(
LPVOID lpAddress,
DWORD dwSize,
DWORD dwFreeType
);