前言
利用windriver做DMA传输的时候,尤其是将数据从板卡传输到PC端时,往往需要分配内存buffer。windriver给了两种方法,Contiguous buffer模式(WDC_DNAContigBufLock)和Scatter/Gather(WDC_DMASGBufLock)模式。PC内存比较小的情况下可以使用Scatter/Gather模式,分配到的内存buffer就是每页固定、多个页数的buffer;PC内存比较大时可以试着使用Contiguous buffer模式,分配到的就是一整块连续的物理内存buffer。
SG模式
- SG模式下buffer的分配主要注意点有几个,第一个就是需要在锁定buf之前,先分配一段虚拟地址,锁定之后,这个虚拟地址会直接和分配buffer的物理首地址绑定;第二个就是dwPages如果不指定大小的,最大只能是256,加上每页4k,最大只能分配到2M(事实上还要减去一点首尾页的开销),所以当需要分配大于2Mbytes的内存buffer时,dwPages需要手动指定大小。以下是具体代码。
BOOL MyDmaOpen(WDC_DEVICE_HANDLE hDev, PVOID *ppBuf, DWORD dwDMABufSize, BOOL BufferMode, WD_DMA **ppDma)
{
DWORD dwStatus;
DWORD dwOptions = DMA_FROM_DEVICE + DMA_ALLOW_64BIT_ADDRESS + DMA_ALLOW_CACHE;
printf("Step2: Function 'MyDmaOpen' begins to run!\n");
// 0 indicates MultiPages Buffer and 1 indicates Contiguous Buffer
if (BufferMode == 0)
{
//Lock a lot of pages buffer for SG Mode
//Allocate a user-mode buffer for Scarrer/Gather DMA
*ppBuf = malloc(dwDMABufSize);
if (!(*ppBuf))
return FALSE;
printf("Preallocated pBuf is 0x%p\n", *ppBuf);
dwOptions +=DMA_LARGE_BUFFER;
//根据需要分配buffer的大小指定需要的页数
DWORD dwPagedNeeded = dwDMABufSize / 4096 + 2;
*ppDma = (WD_DMA *)malloc(sizeof(WD_DMA) + sizeof(WD_DMA_PAGE)*(dwPagedNeeded - WD_DMA_PAGES));
//Allocate and lock a SG DMA buffer
dwStatus = WDC_DMASGBufLock(hDev, ppBuf, dwOptions, dwDMABufSize, ppDma);
if (WD_STATUS_SUCCESS != dwStatus)
{
printf("Failed locking a SG DMA buffer, Error 0x%lx - %s\n", dwStatus, Stat2Str(dwStatus));
return FALSE;
}
}
printf("dwPages is %d, hCard is 0x%x, hDma is 0x%x, Transfer bytes is 0x%x, dwoption is 0x%x\n",
(**ppDma).dwPages, (**ppDma).hCard, (**ppDma).hDma, (**ppDma).dwBytes, (**ppDma).dwOptions);
printf("UserModeAddr is 0x%p, pKernelAddr is 0x%llx\n", (**ppDma).pUserAddr, (ULONG64)(**ppDma).pKernelAddr);
printf("Page 0 PhyscialAddr is 0x%llx, Page dwBytes is 0x%x\n", (ULONG64)(**ppDma).Page[0].pPhysicalAddr, (**ppDma).Page[0].dwBytes);
printf("*ppBuf is 0x%p\n", *ppBuf);
}
- dwOptions可以提前设置,具体参数需要哪些,根据自己的需求查看WD_DMA_OPTIONS结构体;ppBuf是个二级指针,它所指向的指针需要先malloc分配一段虚拟地址,SGBufLock之后,*ppBuf就会指向分配的物理首地址;ppDma同样也是一个二级指针,不过它最终指向的是WD_DMA结构体,在这里我们指定了dwPages的大小,并将其传递给*ppDma,这样最后分配出来的内存buffer才会大于2M,dwDMABufferSize是我们需要分配的内存buffer大小;最后所有有用的相关信息都存放在了WD_DMA结构体里,包括物理地址,虚拟地址和传输长度等。
Contiguous模式
- 分配连续内存buffer时的前面大部分操作跟SG模式下一样,只是锁定内存buffer时的API函数不一样。具体代码如下,SG下的ppBuf是一个输出参数,而这里的ppBuf是一个输入参数,只需要在之前将其初始化一下就好了。最后成功返回的WD_DMA结构体同SG模式下类似,只不过dwPages只有一页,但是dwBytes很大,等同于需要分配的大小,ppBuf二级指针最终指向的虚拟地址也对应着分配的连续物理地址的首地址。
//Allocate and lock a contiguous DMA buffer
dwStatus = WDC_DMAContigBufLock(hDev, ppBuf, dwOptions, dwDMABufSize, ppDma);
if (WD_STATUS_SUCCESS != dwStatus)
{
printf("Failed locking a contiguous DMA buffer, Error 0x%lx - %s\n", dwStatus, Stat2Str(dwStatus));
free(*ppBuf);
return FALSE;
}
- 但是实际操作中,因为PC内存是供电脑上所有程序使用的,分配个几M内存大小没有什么问题,但如果想分配几十M甚至几百M,这种方式往往容易失败。Jungo公司Support里的Technical Document#58有讲到怎样才能分配到大buffer,但是想稳定分配超大Buffer,预分配内存和保留内存的方式才是比较可行的方案。
预分配内存
- 前一篇文章里其实已经提到了怎么进行预分配内存的操作了,Jungo公司Support的Technical Docunment#3也有仔细介绍。
; Host-to-device DMA buffer:
HKR,, "DmaToDeviceCount",0x00010001,0x01 ; Number of preallocated DMA_TO_DEVICE buffers
HKR,, "DmaToDeviceBytes",0x00010001,0x100000 ; Buffer size, in bytes
HKR,, "DmaToDeviceOptions",0x00010001,0x41 ; DMA flags (0x40=DMA_TO_DEVICE
; + 0x1=DMA_KERNEL_BUFFER_ALLOC)
; Device-to-host DMA buffer:
HKR,, "DmaFromDeviceCount",0x00010001,0x01 ; Number of preallocated DMA_FROM_DEVICE buffers
HKR,, "DmaFromDeviceBytes",0x00010001,0x100000 ; Buffer size, in bytes
HKR,, "DmaFromDeviceOptions",0x00010001,0x21 ; DMA flags (0x20=DMA_FROM_DEVICE
; + 0x1=DMA_KERNEL_BUFFER_ALLOC)
- 其实就是在INF文件[UpdateRegistryDevice]里注册进一段配置,这里我们通常注册一个Buffer,可以选择方向性、大小和DMA flags。INF文件配置好之后,重启电脑,再按照之前Configuous buffer分配方式那样即可,同时需要注意的是dwOptions必须和DMA flags保持一致。如果想确保preallocated buffer被使用到了,在运行代码之前打开Debug Monitor,如果使用了就会在分配buffer那里出现‘Using Preallocated buffer’字样。
- 我在用这种方式时,16G内存条能够稳定分配到的内存buffer最大是500M Bytes(且电脑不运行其他无关软件),再大就有概率失败,且想分配的buffer越大失败概率就越大。
保留内存
- 保留内存的方式稍微复杂一些,但胜在分配的内存buffer比较稳定,而且大小不受限制(实际上取决于你的内存条大小,保留内存太大会影响到电脑的使用性能),Jungo公司Support的Technical Docunment#129有讲到怎样去操作。
- 第一步开始之前进行的操作是要保留所需求的内存大小,win7等以上版本的操作系统,在CMD里执行语句
bcdedit /set removememory 1024
执行前后(需重启电脑)可以任务管理器查看可用内存大小,就会发现少了1G内存,这1G内存已经被保留了下来,不会给电脑上其他程序使用了。 - 第二步就是要计算保留内存的物理地址了。打开注册表,找到注册项:HKEY_LOCAL_MACHINE\HARDWARE\RESOURCEMAP\System Resource\Physical Memory.Translated ,在这个资源框里,最高基地址是0x100000000,长度是0x21fe00000,所以保留内存的基地址就是0x31fe00000开始,如果从0x0基地址算起,0x31f200000长度的内存区域也大致等于电脑现有的内存大小。
reserved memory base address = 0x100000000+0x21fe00000=0x31fe00000
- 第三步就是要将保留的内存通过windriver来使用起来。利用windriver将这段保留的内存区域看成一个ISA card(为什么不是PCI,是因为当成PCI设备会重新生成INF文件,与你的板卡相冲突),类似于访问设备的方式去访问这段内存的资源。在写代码实现之前可以先做个小的验证下,利用DriverWizard生成一个新的ISA Project,然后根据前两步得到的保留内存的信息,定义新的内存资源,然后再去尝试访问这段内存是否成功。同样也可以利用DriverWizard来生成相关代码,再去查看它的打开设备的相关信息。
- 以下是将保留内存使用起来的具体代码。
pReservedRegionBase:上述计算的保留内存的物理基地;
qwReservedRegionLength:保留内存区域的长度;
SetMemoryItem:将保留内存区域的相关信息制成一个设备信息集并返回;
LockReservedMemory:根据设备信息集生成一个WDC_DEVICE的句柄并返回
WDC_DEVICE_HANDLE LockReservedMemory(PHYS_ADDR pReservedRegionBase, UINT64 qwReservedRegionLength)
{
DWORD dwStatus;
WDC_DEVICE_HANDLE hDev = NULL;
WD_CARD deviceInfo;
//Set the reserved memory's resources information
BZERO(deviceInfo);
SetMemoryItem(&deviceInfo, pReservedRegionBase, qwReservedRegionLength);
//Get a handle to the reserved memory block
dwStatus = WDC_IsaDeviceOpen(&hDev, &deviceInfo, NULL);
if (WD_STATUS_SUCCESS != dwStatus)
{
printf("Failed opening a WDC device handle, Error 0x%lx - %s\n", dwStatus, Stat2Str(dwStatus));
return NULL;
}
else
printf("Reserved memory device handle gets successfully, 0x%lx - %s\n", dwStatus, Stat2Str(dwStatus));
//return the handle to the reserved memory
return hDev;
}
void SetMemoryItem(WD_CARD *pDeviceInfo, PHYS_ADDR pPhysAddr, UINT64 qwBytes)
{
BZERO(*pDeviceInfo);
WD_ITEMS *pItem = pDeviceInfo->Item;
pDeviceInfo->dwItems = 2;
//1st item:Bus
pItem[0].item = ITEM_BUS;
pItem[0].I.Bus.dwBusType = WD_BUS_ISA;
//2nd item:Memory
pItem[1].item = ITEM_MEMORY;
//Lock the memory for exclusive use
pItem[1].fNotSharable = 1;
//Set the reserved memory's base address
pItem[1].I.Mem.pPhysicalAddr = pPhysAddr;
//Set the reserved memory's size
pItem[1].I.Mem.qwBytes = qwBytes;
//Set the reserved memory's address space
pItem[1].I.Mem.dwBar = 0;
//Map physical memory as cached
pItem[1].I.Mem.dwOptions = WD_ITEM_MEM_DO_NOT_MAP_KERNEL;
}
- 有了保留内存区域的设备句柄之后,就可以像一个设备一样去访问这块内存了,需要注意的是这个设备句柄和板卡的设备句柄需要区分开来。使用保留内存的方式就不需要再使用WDC的API函数了,我16G内存的电脑用这种方式分配1G的内存buffer没有问题。