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
);