前言
在驅動程序(10)裏曾經講過有一種方法,可以把電腦端的一部分內存保留下來製成類似於虛擬設備,只提供給windriver使用,從而作爲DMA傳輸的目的地址。現在講一下當DMA傳輸完成之後,如何去訪問這塊內存資源。實際上,我們就可以將這部分內存資源看成掛載在了虛擬設備上,可以通過訪問真實外部設備一樣去訪問虛擬設備。
虛擬設備製作
#define ReservedMemBase 0x42f000000 //保留內存的物理基地址
#define ReservedMemLength 0x40000000 //保留內存的長度
int main(void)
{
WDC_DEVICE_HANDLE ReservedMemDev = NULL;
//獲取保留內存虛擬生成的設備句柄
ReservedMemDev = GetReservedMemHandle((PHYS_ADDR)ReservedMemBase, ReservedMemLength);
//打印所有設備內存資源的相關信息
//PrintDeviceMemInfo(ReservedMemDev);
//獲得該設備句柄的設備信息
WDC_DEVICE *device = (WDC_DEVICE*)ReservedMemDev;
//獲取保留內存經過映射後的用戶態下的虛擬基地址
DWORD *pBuf = (DWORD*)device->pAddrDesc->pUserDirectMemAddr;
printf("Reserved Memory UserMode Address is 0x%p, length is 0x%x\n", pBuf, MappingMemLength);
printf("\n");
}
WDC_DEVICE_HANDLE GetReservedMemHandle(PHYS_ADDR pReservedMemBase, DWORD64 qwReservedMemLength)
{
//configurate information of WD_CARD_REGISTER
WD_CARD_REGISTER cardReg;
BZERO(cardReg);
cardReg.Card.dwItems = 2;
cardReg.Card.Item[0].item = ITEM_BUS;
cardReg.Card.Item[0].I.Bus.dwBusType = WD_BUS_ISA;
cardReg.Card.Item[1].item = ITEM_MEMORY; //item類型
cardReg.Card.Item[1].fNotSharable = TRUE; //不可共享
cardReg.Card.Item[1].I.Mem.dwBar = 0; //地址空間段
cardReg.Card.Item[1].I.Mem.pPhysicalAddr = pReservedMemBase; //物理基地址
cardReg.Card.Item[1].I.Mem.qwBytes = qwReservedMemLength; //物理內存長度
cardReg.Card.Item[1].I.Mem.dwOptions = WD_ITEM_MEM_DO_NOT_MAP_KERNEL; //內核態下虛擬地址不映射
//register the reserved memory and get device handle
DWORD dwStatus;
WDC_DEVICE_HANDLE ReservedMemDev = NULL;
dwStatus = WDC_IsaDeviceOpen(&ReservedMemDev, &cardReg.Card, NULL);
if (WD_STATUS_SUCCESS != dwStatus)
{
printf("Failed opening a reserved memory device handle, Error 0x%lx - %s\n", dwStatus, Stat2Str(dwStatus));
return NULL;
}
else
printf("Reserved memory device handle gets successfully\n");
return ReservedMemDev;
}
void PrintDeviceMemInfo(WDC_DEVICE_HANDLE hDev)
{
if (hDev)
{
//Get WDC_DEVICE from WDC_DEVICE_HANDLE
WDC_DEVICE *device = (WDC_DEVICE*)hDev;
//print all address spaces information including UserDirectMemAddr and qwBytes
printf("There are %d numbers of address spaces found on the device\n", device->dwNumAddrSpaces);
DWORD i;
for (i = 0; i < device->dwNumAddrSpaces; i++) //多個BAR存在時
{
printf("[BAR %d]: UserMemAddrBase 0x%p, size 0x%x\n", device->pAddrDesc[i].dwAddrSpace, device->pAddrDesc[i].pUserDirectMemAddr, device->pAddrDesc[i].qwBytes);
}
printf("\n");
}
}
製作虛擬設備的方法和驅動程序(10)裏講的類似,均是先把保留內存的物理基地址和長度配置進來,例如參數ReservedMemBase和ReservedMemLength,具體哪些參數需要配置,哪些不需要,可以參考WD_CARD_REGISTER STRUCT的定義;然後利用windriver提供的API函數WDC_IsaDeviceOpen()註冊,也可以採用WDC_PciDeviceOpen()等函數,獲取虛擬設備生成的句柄。接下來就可以通過這個設備句柄去訪問虛擬設備上的保留內存資源了,這裏主要有兩種方法。
從虛擬設備上內存中讀取數據
- 第一種方法是使用API函數WDC_ReadAddr32(),我們通過虛擬設備製作已經得到了設備句柄,地址空間段也是提前配置的參數,所以只需要改變偏移地址就可以訪問到內存上的數據了。這裏我們可以先自定義一個簡單的ReadReg32()函數,直接返回讀取到的數據。唯一的缺點就在於這種方式只能以32bit/64bit(可選)來讀取數據,也就是數據必須以4個字節編碼,數據只能一個一個地讀取,這對於驅動程序與內核態的交互來說效率是很低的。
UINT32 ReadReg32(WDC_DEVICE_HANDLE hDev, DWORD dwAddrSpace, DWORD dwOffset)
{
UINT32 u32Data;
WDC_ReadAddr32(hDev, dwAddrSpace, dwOffset, &u32Data);
return u32Data;
}
- 第二種方法是先講設備句柄中的WDC_DEVICE信息提取出來,具體需要哪些參數可以參考WDC_DEICE STRUCT的定義。如同PrintDeviceMemInfo()裏所寫那樣,獲取到設備句柄內該段內存資源的pUserDirectMemAddr地址,也就是我們最早配置的保留內存經過內核映射過的用戶態下的虛擬基地址,該地址所存放的第一個數據就是保留內存物理基地址所存放的第一個數據,且物理地址連續的話,虛擬地址也是連續的。接着就可以利用WDC_ReadMem32()函數或者將該虛擬基地址指向一個指針,訪問第一個數據之後,移動指針不停地訪問接下來的所有數據。這種方法最終可以生成一個指針供其他應用使用,訪問數據的效率要提高很多。
//獲得該設備句柄的設備信息
WDC_DEVICE *device = (WDC_DEVICE*)ReservedMemDev;
//獲取保留內存經過映射後的用戶態下的虛擬基地址
DWORD *pBuf = (DWORD*)device->pAddrDesc->pUserDirectMemAddr;
#define WDC_ReadMem32(addr, off) \
dtoh32(*(volatile UINT32 *)((UPTR)(addr) + (UPTR)(off)))