前言
利用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沒有問題。