将 UMDF 外设驱动程序连接到串行端口

SerCx2 托管串行端口上外围设备的 UMDF 驱动程序需要某些硬件资源来操作设备。 这些资源中包含驱动程序打开与串行端口的逻辑连接所需的信息。 其他资源可能包括中断以及一个或多个 GPIO 输入或输出引脚。

此驱动程序实现 IPnpCallbackHardware2 接口,并在调用驱动程序的 IDriverEntry::OnDeviceAdd 方法期间向 Windows 驱动程序框架注册此接口。 框架调用 IPnpCallbackHardware2 接口中的方法,以通知驱动程序设备电源状态的更改。

串行连接的外围设备进入未初始化的 D0 设备电源状态后,驱动程序框架将调用驱动程序的 IPnpCallbackHardware2::OnPrepareHardware 方法,告知驱动程序准备此设备以供使用。 在此调用期间,驱动程序接收两个硬件资源列表作为输入参数。 pWdfResourcesRaw 参数指向原始资源列表,pWdfResourcesTranslated 参数指向已翻译的资源列表。 这两个参数都是指向 IWDFCmResourceList 对象的指针。 转换的资源包括外围驱动程序建立与串行连接的外围设备的逻辑连接所需的连接 ID。

若要使 UMDF 外围驱动程序能够接收其资源列表中的连接 ID,安装驱动程序的 INF 文件必须在其特定于 WDF 的 DDInstall 节中包含以下指令:

UmdfDirectHardwareAccess = AllowDirectHardwareAccess 有关此指令的详细信息,请参阅 在 INF 文件中指定 WDF 指令。 有关用于生成使用此指令的相应 INF 文件) 的 INX 文件 (示例,请参阅 WDK 驱动程序示例中的 SpbAccelerometer。

下面的代码示例演示驱动程序的 OnPrepareHardware 方法如何从 pWdfResourcesTranslated 参数获取连接 ID。

BOOLEAN fConnectIdFound = FALSE;
BOOLEAN fDuplicateFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;

resourceCount = pWdfResourcesTranslated->GetCount();

// Loop through the resources and save the relevant ones.
for (ULONG ix = 0; ix < resourceCount; ix++)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;

    pDescriptor = pWdfResourcesTranslated->GetDescriptor(ix);

    if (pDescriptor == NULL)
    {
        hr = E_POINTER;
        break;
    }

    // Determine the resource type.
    switch (pDescriptor->Type)
    { 
        case CmResourceTypeConnection:
            {
                // Check against the expected connection types.
                UCHAR Class = pDescriptor->u.Connection.Class;
                UCHAR Type = pDescriptor->u.Connection.Type;

                if (Class == CM_RESOURCE_CONNECTION_CLASS_SERIAL)
                {
                    if (Type == CM_RESOURCE_CONNECTION_TYPE_SERIAL_UART)
                    {
                        if (fConnIdFound == FALSE)
                        {
                            // Save the serial controller's connection ID.
                            connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
                            connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
                            fConnectIdFound = TRUE;
                        }
                        else
                        {
                            fDuplicateFound = TRUE;
                        }
                    }
                }

                if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
                {
                    // Check for GPIO pin resource.
                    ...
                }
            }
            break;

        case CmResourceTypeInterrupt:
            {
                // Check for interrupt resources.
                ...
            }
            break;

        default:
            // Ignore all other resource descriptors.
            break;
    }
}

前面的代码示例将串行连接的外围设备的连接 ID 复制到名为 的 connectionId变量中。 下面的代码示例演示如何将连接 ID 合并到可用于标识外围设备连接到的串行控制器的设备路径名称中。

WCHAR szTargetPath[100];
HRESULT hres;

// Create the device path using the well-known resource hub
// path name and the connection ID.
//
hres = StringCbPrintfW(&szTargetPath[0],
                       sizeof(DevicePath),
                       L"\\\\.\\RESOURCE_HUB\\%0*I64x",
                       (size_t)(sizeof(LARGE_INTEGER) * 2),
                       connectionId.QuadPart);
if (FAILED(hres))
{
     // Error handling
     ...
}

前面的代码示例将串行控制器的设备路径名称写入数组中 szTargetPath 。 下面的代码示例使用此路径名称打开串行控制器的文件句柄。

UMDF_IO_TARGET_OPEN_PARAMS openParams;

openParams.dwShareMode = 0;
openParams.dwCreationDisposition = OPEN_EXISTING;
openParams.dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED;
hres = pRemoteTarget->OpenFileByName(&szTargetPath[0],
                                     (GENERIC_READ | GENERIC_WRITE),
                                     &openParams);
if (FAILED(hres))
{
    // Error handling
    ...
}

在前面的代码示例中 pRemoteTarget , 参数是指向 IWDFRemoteTarget 对象的指针。 如果调用 IWDFRemoteTarget::OpenFileByName 方法成功,则串行连接的外围设备的驱动程序可以使用 IWDFRemoteTarget 对象将 I/O 请求发送到串行控制器。

若要向外围设备发送读取或写入请求,驱动程序首先调用此对象的 IWDFRemoteTarget::FormatRequestForReadIWDFRemoteTarget::FormatRequestForWrite 方法来设置请求的格式。 (IWDFRemoteTarget 接口从 IWDFIoTarget 接口继承这两种方法。)

若要向串行控制器发送 I/O 控制请求,驱动程序首先调用 IWDFRemoteTarget::FormatRequestForIoctl 方法来设置请求的格式。 (IWDFRemoteTarget 接口从 IWDFIoTarget 接口继承此方法。) 接下来,驱动程序调用 IWDFIoRequest::Send 方法将 I/O 控制请求发送到串行连接的外围设备。

在以下代码示例中,外围驱动程序将 I/O 控制请求发送到串行控制器。

HRESULT hres;
IWDFMemory *pInputMemory = NULL;

// Create a new I/O request.
if (SUCCEEDED(hres))
{
    hres = pWdfDevice->CreateRequest(NULL, 
                                     pWdfDevice, 
                                     &pWdfIoRequest);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Allocate memory for the input buffer.
if (SUCCEEDED(hres))
{
    hres = pWdfDriver->CreatePreallocatedWdfMemory(pInBuffer, 
                                                   inBufferSize, 
                                                   NULL,
                                                   pWdfIoRequest,
                                                   &pInputMemory);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Format the request to be an I/O control request.
if (SUCCEEDED(hres))
{
    hres = pRemoteTarget->FormatRequestForIoctl(pWdfIoRequest,
                                                ioctlCode,
                                                NULL,
                                                pInputMemory, 
                                                NULL, 
                                                NULL,
                                                NULL);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

// Send the request to the serial controller.
if (SUCCEEDED(hres))
{
    ULONG Flags = fSynchronous ? WDF_REQUEST_SEND_OPTION_SYNCHRONOUS : 0;

    if (!fSynchronous)
    {
        pWdfIoRequest->SetCompletionCallback(pCallback, NULL); 
    }

    hres = pWdfIoRequest->Send(pRemoteTarget, Flags, 0);

    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

if (fSynchronous || FAILED(hres))
{
    pWdfIoRequest->DeleteWdfObject();
    SAFE_RELEASE(pWdfIoRequest);
}

前面的代码示例执行以下操作:

  1. 变量 pWdfDevice 是指向框架设备对象的 IWDFDevice 接口的指针,该对象表示串行连接的外围设备。 IWDFDevice::CreateRequest 方法创建 I/O 请求,并将此请求封装在 参数指向的 IWDFIoRequest 接口实例中pWdfIoRequest。 稍后将删除 I/O 请求, (请参阅步骤 6) 。 此实现在一定程度上效率低下,因为它会为每个发送的 I/O 请求创建并删除请求对象。 更有效的方法是对一系列 I/O 请求重复使用同一请求对象。 有关详细信息,请参阅 重用框架请求对象

  2. 变量 pWdfDriver 是指向表示外围驱动程序的框架驱动程序对象的 IWDFDriver 接口的指针。 pInBufferinBufferSize 变量指定 I/O 控制请求的输入缓冲区的地址和大小。 IWDFDriver::CreatePreallocatedWdfMemory 方法为输入缓冲区创建框架内存对象,并将所pWdfIoRequest指向的 IWDFIoRequest 对象指定为内存对象的父对象。

  3. 变量 pWdfRemoteTarget 是从前面的代码示例中的 OpenFileByName 调用获取的远程目标指针。 IWDFRemoteTarget::FormatRequestForIoctl 方法格式化 I/O 控件操作的请求。 变量 ioctlCode 设置为串行 I/O 请求接口中表中列出的 I/O 控制代码之一。

  4. fSynchronous如果要同步发送 I/O 控制请求,则变量为 TRUE;如果要异步发送,则变量为 FALSE。 变量 pCallback 是指向以前创建的 IRequestCallbackRequestCompletion 接口的指针。 如果要异步发送请求,则对 IWDFIoRequest::SetCompletionCallback 方法的调用将注册此接口。 稍后,将调用 IRequestCallbackRequestCompletion::OnCompletion 方法,以便在请求异步完成时通知驱动程序。

  5. Send 方法将格式化的写入请求发送到串行连接的外围设备。 变量 Flags 指示是以同步方式还是异步发送写入请求。

  6. 如果请求是同步发送的, 则 IWDFIoRequest::D eleteWdfObject 方法将删除由 pWdfIoRequest 指向的 I/O 请求对象和 由 pInputMemory指向的子对象。 IWDFIoRequest 接口从 IWDFObject 接口继承此方法。 如果请求是异步发送的,则应稍后在驱动程序的 OnCompletion 方法中调用 DeleteWdfObject 方法。