WDDM KMOD驱动设备的创建与启动
创建设备 DxgkDdiAddDevice/BddDdiAddDevice
WDM驱动的一个核心思想是代码的重用,这样如果是同一型号的显卡芯片,可以使用同一套代码,而对于每一个芯片,只需要保留相关的上下文即可。这里“代码”就是WDM驱动架构中的DRIVER_OBJECT,而每个芯片就叫做DEVICE_OBJECT。
熟悉WDM驱动的同学可知道,如果一个系统中,有多个同样的物理显卡,WDM驱动会对每个设备通过调用DRVIER_OBJCCT结构体中DriverExtension成员变量的AddDevice。AddDevice函数会对总线驱动枚举出来的每一个PDO设备使用IoCreateDevice函数创建一个FDO,然后再附加到这个PDO上。而在创建每一个设备时,每个设备都可以有一个自己的DeviceExtension,这个指针指向的内存可由驱动设计者自行定义。
说完了WDM驱动的AddDevice,我们再来看显卡驱动的的回调函数DxgkDdiAddDevice成员变量,这个回调函数的原型如下:
PDXGKDDI_ADD_DEVICE DxgkDdiAddDevice;
其具本的定义如下:
NTSTATUS
DXGKDDI_ADD_DEVICE(
IN_CONST_PDEVICE_OBJECT PhysicalDeviceObject,
OUT_PPVOID MiniportDeviceContext
);
可以看到,这个函数的第一个变量为const DEVICE_OBJECT类型,这其实就是总线设备创建的PDO,而第二个参数为一个指针的指针,其命名为MiniportDeviceContext,表示该成员为Mini小端口设备的上下文,而这个上下文是保存在其FDO扩展结构体DeviceExtension指针指向的结构体中一个成员中。
mini小端口驱动和类驱动的这种设计方法在Windows驱动中是屡见不鲜,像HID类驱动与HID MiniPort驱动,磁盘类驱动与磙盘MINPort驱动等。
DxgkDdiAddDevice函数创建这个上下文并返回给框架驱动后,后续框架驱动将使用这个上下文通过其余的回调函数与该Mini小端口驱动相通讯。
下面我们来看一下DxgkDdiAddDevice函数的具体实现:
NTSTATUS
BddDdiAddDevice(
_In_ DEVICE_OBJECT* pPhysicalDeviceObject,
_Outptr_ PVOID* ppDeviceContext)
{
PAGED_CODE();
if ((pPhysicalDeviceObject == NULL) ||
(ppDeviceContext == NULL))
{
BDD_LOG_ERROR2("One of pPhysicalDeviceObject (0x%I64x), ppDeviceContext (0x%I64x) is NULL",
pPhysicalDeviceObject, ppDeviceContext);
return STATUS_INVALID_PARAMETER;
}
*ppDeviceContext = NULL;
BASIC_DISPLAY_DRIVER* pBDD = new(NonPagedPoolNx) BASIC_DISPLAY_DRIVER(pPhysicalDeviceObject);
if (pBDD == NULL)
{
BDD_LOG_LOW_RESOURCE0("pBDD failed to be allocated");
return STATUS_NO_MEMORY;
}
*ppDeviceContext = pBDD;
return STATUS_SUCCESS;
}
BddDdiAddDevice函数的实现很简单,在函数内部作必要的指针判断后,就new以一个NonPagedPoolNx类,再将new出来的这个类实例的指针保存在ppDeviceContext中。
关于在内核中使用new delete运行算的重载可详见:Windows内核驱动中使用new和delete
设备启动DxgkDdiStartDevice/BddDdiStartDevice
设备创建成功后,是需要启动的。这个启动执行的是IRP主功能号为IRP_MJ_PNP,子功能号为IRP_MN_START_DEVICE的函数。
对于KMOD驱动,首先使用获取我们在BddDdiAddDevice返回的VOID*类型指针转换成自定义类BASIC_DISPLAY_DRIVER指针,然后调用其成员函数StartDevice函数。
NTSTATUS
BddDdiStartDevice(
_In_ VOID* pDeviceContext,
_In_ DXGK_START_INFO* pDxgkStartInfo,
_In_ DXGKRNL_INTERFACE* pDxgkInterface,
_Out_ ULONG* pNumberOfViews,
_Out_ ULONG* pNumberOfChildren)
{
PAGED_CODE();
BDD_ASSERT_CHK(pDeviceContext != NULL);
BASIC_DISPLAY_DRIVER* pBDD = reinterpret_cast<BASIC_DISPLAY_DRIVER*>(pDeviceContext);
return pBDD->StartDevice(pDxgkStartInfo, pDxgkInterface, pNumberOfViews, pNumberOfChildren);
}
BddDdiStartDevice的四个参数原封不动地传给类BASIC_DISPLAY_DRIVER的成员函数StartDevice。
DXGK_START_INFO结构体
DXGK_START_INFO结构体是框架驱动传给KMOD驱动的参数,KMOD驱动需根据传入的参数进行相关的DMA buffer内存分配。
DXGK_START_INFO结构体成员变量RequiredDmaQueueEntry保存了需要分配的DMA队列中预分配BUFFER的数量。
AdapterGuid和AdapterLuid则为适配器的标识。
typedef struct _DXGK_START_INFO {
ULONG RequiredDmaQueueEntry;
GUID AdapterGuid;
LUID AdapterLuid;
} DXGK_START_INFO, *PDXGK_START_INFO;
DXGKRNL_INTERFACE结构体
DXGKRNL_INTERFACE结构体包含一个句柄和若干的回调函数,而这个句柄和函数都是由系统框架实现。所以在KMOD驱动中, 我们可以通过该系列函数获取相关的系统框架或硬件相关的信息。
如我们在开发PCI/E驱动时,我们可将IRP_MN_START_DEVICE IRP传给PDO,即可获取该设备的硬件资源信息。
PCM_PARTIAL_RESOURCE_LIST pTranslatedResource,pRawResource;
pTranslatedResource = &(pStackLocation->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList);
pRawResource = &(pStackLocation->Parameters.StartDevice.AllocatedResources->List[0].PartialResourceList);
而在KMOD驱动由于是Mini小端口驱动,此IRP已经被系统框架处理,故我们可以借助DXGKRNL_INTERFACE结构体结构体的DXGKCB_GET_DEVICE_INFORMATION/DxgkCbGetDeviceInformation函数与之通信,通过系统框架间接获取硬件资源信息。
当然DXGKRNL_INTERFACE结构体能实现的功能远不止这些,其另外一部分功能是与系统框架驱动的通信。
pNumberOfViews
pNumberOfViews为KMOD需要返回给系统框架的视频源数量。如支持扩展屏的显卡我们可以返回2,一个为主显示源,一个为扩展显示源。
pNumberOfChildren
为在显卡上枚举到的显示器数量。
详细代码如下:
NTSTATUS BASIC_DISPLAY_DRIVER::StartDevice(_In_ DXGK_START_INFO* pDxgkStartInfo,
_In_ DXGKRNL_INTERFACE* pDxgkInterface,
_Out_ ULONG* pNumberOfViews,
_Out_ ULONG* pNumberOfChildren)
{
PAGED_CODE();
BDD_ASSERT(pDxgkStartInfo != NULL);
BDD_ASSERT(pDxgkInterface != NULL);
BDD_ASSERT(pNumberOfViews != NULL);
BDD_ASSERT(pNumberOfChildren != NULL);
RtlCopyMemory(&m_StartInfo, pDxgkStartInfo, sizeof(m_StartInfo));
RtlCopyMemory(&m_DxgkInterface, pDxgkInterface, sizeof(m_DxgkInterface));
RtlZeroMemory(m_CurrentModes, sizeof(m_CurrentModes));
m_CurrentModes[0].DispInfo.TargetId = D3DDDI_ID_UNINITIALIZED;
// Get device information from OS.
NTSTATUS Status = m_DxgkInterface.DxgkCbGetDeviceInformation(m_DxgkInterface.DeviceHandle, &m_DeviceInfo);
if (!NT_SUCCESS(Status))
{
BDD_LOG_ASSERTION1("DxgkCbGetDeviceInformation failed with status 0x%I64x",
Status);
return Status;
}
// Ignore return value, since it's not the end of the world if we failed to write these values to the registry
RegisterHWInfo();
// TODO: Uncomment the line below after updating the TODOs in the function CheckHardware
// Status = CheckHardware();
if (!NT_SUCCESS(Status))
{
return Status;
}
// This sample driver only uses the frame buffer of the POST device. DxgkCbAcquirePostDisplayOwnership
// gives you the frame buffer address and ensures that no one else is drawing to it. Be sure to give it back!
Status = m_DxgkInterface.DxgkCbAcquirePostDisplayOwnership(m_DxgkInterface.DeviceHandle, &(m_CurrentModes[0].DispInfo));
if (!NT_SUCCESS(Status) || m_CurrentModes[0].DispInfo.Width == 0)
{
// The most likely cause of failure is that the driver is simply not running on a POST device, or we are running
// after a pre-WDDM 1.2 driver. Since we can't draw anything, we should fail to start.
return STATUS_UNSUCCESSFUL;
}
m_Flags.DriverStarted = TRUE;
*pNumberOfViews = MAX_VIEWS;
*pNumberOfChildren = MAX_CHILDREN;
return STATUS_SUCCESS;
}
从代码上来看:
- 首先保存两个输出结构体
- 再根据DXGKRNL_INTERFACE结构体的成员变量DxgkCbGetDeviceInformation获取设备硬件信息。
- 写注册表RegisterHWInfo。这里写的注册表信息主要是与PDO显示相关的友好信息。
- 根据DXGKRNL_INTERFACE结构体的成员变量DxgkCbAcquirePostDisplayOwnership获取显示信息。如宽,高,每象系的这节数,色采模式,当前显示模式的物理地址PHYSICAL_ADDRESS等。
- 最后返回视频源和显示器数量均为1.
附相关结构体:
DXGKRNL_INTERFACE
typedef struct _DXGKRNL_INTERFACE {
ULONG Size;
ULONG Version;
HANDLE DeviceHandle; //所以函数的入口参数句柄
DXGKCB_EVAL_ACPI_METHOD DxgkCbEvalAcpiMethod;
DXGKCB_GET_DEVICE_INFORMATION DxgkCbGetDeviceInformation; //硬件信息
DXGKCB_INDICATE_CHILD_STATUS DxgkCbIndicateChildStatus;
DXGKCB_MAP_MEMORY DxgkCbMapMemory;
DXGKCB_QUEUE_DPC DxgkCbQueueDpc;
DXGKCB_QUERY_SERVICES DxgkCbQueryServices;
DXGKCB_READ_DEVICE_SPACE DxgkCbReadDeviceSpace;
DXGKCB_SYNCHRONIZE_EXECUTION DxgkCbSynchronizeExecution;
DXGKCB_UNMAP_MEMORY DxgkCbUnmapMemory;
DXGKCB_WRITE_DEVICE_SPACE DxgkCbWriteDeviceSpace;
DXGKCB_IS_DEVICE_PRESENT DxgkCbIsDevicePresent;
DXGKCB_GETHANDLEDATA DxgkCbGetHandleData;
DXGKCB_GETHANDLEPARENT DxgkCbGetHandleParent;
DXGKCB_ENUMHANDLECHILDREN DxgkCbEnumHandleChildren;
DXGKCB_NOTIFY_INTERRUPT DxgkCbNotifyInterrupt;
DXGKCB_NOTIFY_DPC DxgkCbNotifyDpc;
DXGKCB_QUERYVIDPNINTERFACE DxgkCbQueryVidPnInterface;
DXGKCB_QUERYMONITORINTERFACE DxgkCbQueryMonitorInterface;
DXGKCB_GETCAPTUREADDRESS DxgkCbGetCaptureAddress;
DXGKCB_LOG_ETW_EVENT DxgkCbLogEtwEvent;
DXGKCB_EXCLUDE_ADAPTER_ACCESS DxgkCbExcludeAdapterAccess;
#if DXGKDDI_INTERFACE_VERSION >= DXGKDDI_INTERFACE_VERSION_WIN8)
DXGKCB_CREATECONTEXTALLOCATION DxgkCbCreateContextAllocation;
DXGKCB_DESTROYCONTEXTALLOCATION DxgkCbDestroyContextAllocation;
DXGKCB_SETPOWERCOMPONENTACTIVE DxgkCbSetPowerComponentActive;
DXGKCB_SETPOWERCOMPONENTIDLE DxgkCbSetPowerComponentIdle;
DXGKCB_ACQUIRE_POST_DISPLAY_OWNERSHIP DxgkCbAcquirePostDisplayOwnership;//显示信息
DXGKCB_POWERRUNTIMECONTROLREQUEST DxgkCbPowerRuntimeControlRequest;
DXGKCB_SETPOWERCOMPONENTLATENCY DxgkCbSetPowerComponentLatency;
DXGKCB_SETPOWERCOMPONENTRESIDENCY DxgkCbSetPowerComponentResidency;
DXGKCB_COMPLETEFSTATETRANSITION DxgkCbCompleteFStateTransition;
#endif
#if (DXGKDDI_INTERFACE_VERSION >= DXGKDDI_INTERFACE_VERSION_WDDM1_3_M1)
DXGKCB_COMPLETEPSTATETRANSITION DxgkCbCompletePStateTransition;
#endif
} DXGKRNL_INTERFACE, *PDXGKRNL_INTERFACE;
DXGK_DISPLAY_INFORMATION
typedef struct _DXGK_DISPLAY_INFORMATION {
UINT Width;
UINT Height;
UINT Pitch;
D3DDDIFORMAT ColorFormat;
PHYSICAL_ADDRESS PhysicAddress;
D3DDDI_VIDEO_PRESENT_TARGET_ID TargetId;
ULONG AcpiId;
} DXGK_DISPLAY_INFORMATION, *PDXGK_DISPLAY_INFORMATION;