WDDM显卡驱动
+ -

WDDM KMOD驱动设备的创建与启动

2021-06-30 607 0

创建设备 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;

0 篇笔记 写笔记

WDDM KMOD驱动设备的创建与启动
创建设备 DxgkDdiAddDevice/BddDdiAddDeviceWDM驱动的一个核心思想是代码的重用,这样如果是同一型号的显卡芯片,可以使用同一套代码,而对于每一个芯片,只需要保留相关的上下文即可。这里“代码”就是WDM驱动架构中的DRIVER_OBJECT,而每个芯片就叫做DEVICE_......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!