幀緩沖區保留
對于必須在電源轉換期間將幀緩沖區的保留部分保存到系統內存的驅動程序,Dxgkrnl 會在適配器初始化時對所需內存進行用量認可。 如果驅動程序報告 IOMMU 隔離支持,則 Dxgkrnl 將在查詢物理適配器上限后立即調用 DXGKDDI_QUERYADAPTERINFO,內容如下:
- Type 為 DXGKQAITYPE_FRAMEBUFFERSAVESIZE
- 輸入類型為 UINT,即物理適配器索引。
- 輸出類型為 DXGK_FRAMEBUFFERSAVEAREA,應為驅動程序在電源轉換期間保存幀緩沖區保留區域所需的最大大小。
Dxgkrnl 會按驅動程序指定的數量進行用量認可,以確保始終能根據請求獲取物理頁面。 此操作是通過為每個物理適配器創建一個唯一的區域對象來完成的,該對象為最大尺寸指定了一個非零值。
驅動程序報告的最大大小必須是 PAGE_SIZE 的倍數。
與幀緩沖區之間的傳輸可以在驅動程序選擇的時間進行。 為了幫助傳輸,Dxgkrnl 向內核模式驅動程序提供了上表中的最后四個回調。 這些回調可用于映射適配器初始化時創建的區域對象的相應部分。
在調用這四個回調函數時,驅動程序必須始終為 LDA 鏈中的主導設備提供 hAdapter。
驅動程序有兩個實現幀緩沖區保留的選項:
- (首選方法)驅動程序應使用 DXGKDDI_QUERYADAPTERINFO 來調用為每個物理適配器分配空間,以指定每個適配器所需的存儲空間。 在電源轉換時,驅動程序應每次保存或恢復一個物理適配器的內存。 此內存會被分割成多個區域對象,每個物理適配器一個。
- ?(可選)驅動程序可以將所有數據保存或還原到單個共享區域對象中。 要執行此操作,可在 DXGKDDI_QUERYADAPTERINFO 調用中為物理適配器 0 指定一個較大的最大大小,然后為所有其他物理適配器指定一個零值。 這樣,驅動程序就可以將整個區域對象固定下來,供所有物理適配器的所有保存/還原操作使用。 這種方法的主要缺點是需要一次性鎖定更多內存,因為它不支持只將內存的子范圍固定到 MDL 中。 因此,在內存壓力下,這種操作更容易失敗。 此外,驅動程序還應使用正確的頁面偏移量將 MDL 中的頁面映射到 GPU。
?驅動程序應執行以下任務,以完成向幀緩沖區或從幀緩沖區的傳輸:
-
在初始化過程中,驅動程序應使用其中一個分配回調例程預先分配一小塊 GPU 可訪問內存。 如果無法一次性映射/鎖定整個區域對象,則使用該內存來幫助確保向前推進。
-
在電源轉換時,驅動程序應首先調用?Dxgkrnl?以固定幀緩沖區。 成功后,Dxgkrnl?會為驅動程序提供一個 MDL,用于鎖定映射到 IOMMU 的頁面。 然后,驅動程序就可以通過任何對硬件最有效的方式直接向這些頁面執行傳輸。 然后,驅動程序應調用?Dxgkrnl?來解鎖/取消映射內存。
-
如果?Dxgkrnl?無法立即鎖定整個幀緩沖區,則驅動程序必須嘗試使用初始化時分配的預分配緩沖區來向前推進。 在這種情況下,驅動程序按小塊來執行傳輸。 在每次迭代傳輸過程中(針對每個區塊),驅動程序必須要求?Dxgkrnl?提供一個可將結果復制到其中的區域對象映射范圍。 然后,驅動程序必須在下一次迭代之前取消區域對象的映射。
下面的偽代碼是實現這種算法的一個示例。
#define SMALL_SIZE (PAGE_SIZE)PMDL PHYSICAL_ADAPTER::m_SmallMdl;
PMDL PHYSICAL_ADAPTER::m_PinnedMdl;NTSTATUS PHYSICAL_ADAPTER::Init()
{DXGKARGCB_ALLOCATEPAGESFORMDL Args = {};Args.TotalBytes = SMALL_SIZE;// Allocate small buffer up front for forward progress transfersStatus = DxgkCbAllocatePagesForMdl(SMALL_SIZE, &Args);m_SmallMdl = Args.pMdl;...
}NTSTATUS PHYSICAL_ADAPTER::OnPowerDown()
{ Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);if(!NT_SUCCESS(Status)){m_pPinnedMdl = NULL;}if(m_pPinnedMdl != NULL){ // Normal GPU copy: frame buffer -> m_pPinnedMdlGpuCopyFromFrameBuffer(m_pPinnedMdl, Size);DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);}else{SIZE_T Offset = 0;while(Offset != TotalSize){SIZE_T MappedOffset = Offset;PVOID pCpuPointer;Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);if(!NT_SUCCESS(Status)){// Driver must handle failure here. Even a 4KB mapping may// not succeed. The driver should attempt to cancel the// transfer and reset the adapter.}GpuCopyFromFrameBuffer(m_pSmallMdl, SMALL_SIZE);RtlCopyMemory(pCpuPointer + MappedOffset, m_pSmallCpuPointer, SMALL_SIZE);DxgkCbUnmapFrameBufferPointer(pCpuPointer);Offset += SMALL_SIZE;}}
}NTSTATUS PHYSICAL_ADAPTER::OnPowerUp()
{Status = DxgkCbPinFrameBufferForSave(&m_pPinnedMdl);if(!NT_SUCCESS(Status)){m_pPinnedMdl = NULL;}if(pPinnedMemory != NULL){// Normal GPU copy: m_pPinnedMdl -> frame bufferGpuCopyToFrameBuffer(m_pPinnedMdl, Size);DxgkCbUnpinFrameBufferForSave(m_pPinnedMdl);}else{SIZE_T Offset = 0;while(Offset != TotalSize){SIZE_T MappedOffset = Offset;PVOID pCpuPointer;Status = DxgkCbMapFrameBufferPointer(SMALL_SIZE, &MappedOffset, &pCpuPointer);if(!NT_SUCCESS(Status)){// Driver must handle failure here. Even a 4KB mapping may// not succeed. The driver should attempt to cancel the// transfer and reset the adapter.}RtlCopyMemory(m_pSmallCpuPointer, pCpuPointer + MappedOffset, SMALL_SIZE);GpuCopyToFrameBuffer(m_pSmallMdl, SMALL_SIZE);DxgkCbUnmapFrameBufferPointer(pCpuPointer);Offset += SMALL_SIZE;}}
}
硬件保留內存
在設備連接到 IOMMU 之前,VidMm 會映射硬件保留內存。
VidMm 會自動處理任何作為帶有 PopulatedFromSystemMemory 標記的內存段報告的內存。 VidMm 會根據提供的物理地址來映射該內存。
對于未通過段公開的專用硬件保留區域,VidMm 會調用 DXGKDDI_QUERYADAPTERINFO 驅動程序來查詢其范圍。 所提供的范圍不得與 NTOS 內存管理器使用的任何內存區域相重疊;VidMm 會驗證是否存在此類交叉。 此驗證可確保驅動程序不會意外報告超出保留范圍的物理內存區域,因而違反該功能的安全保證。
調用一次查詢是為了查詢所需的范圍數量,而隨后的第二次調用是為了填充保留范圍的數組。
測試
如果驅動程序選擇使用此功能,HLK 測試將掃描驅動程序的導入表,以確保沒有調用以下 Mm 函數:
- MmAllocateContiguousMemory
- MmAllocateContiguousMemorySpecifyCache
- MmFreeContiguousMemory
- MmAllocatePagesForMdl
- MmAllocatePagesForMdlEx
- MmFreePagesFromMdl
- MmProbeAndLockPages
?所有連續內存和 MDL 的內存分配都應通過?Dxgkrnl?的回調接口,使用列出的函數來進行。 驅動程序也不應鎖定任何內存。?Dxgkrnl?會為驅動程序管理鎖定的頁面。 一旦重新映射了內存,提供給驅動程序的頁面邏輯地址可能不再與物理地址一致。