用户模式 SPB 外设驱动程序的硬件资源

本主题中的代码示例演示 用户模式驱动程序框架 (UMDF) 驱动程序如何在 简单外围总线 (SPB) 获取运行设备所需的硬件资源。 这些资源中包含驱动程序用于建立与设备的逻辑连接的信息。 其他资源可能包括中断以及一个或多个 GPIO 输入或输出引脚。 (GPIO 引脚是配置为输入或输出的通用 I/O 控制器设备上的引脚;有关详细信息,请参阅 GPIO 控制器驱动程序。) 与内存映射的设备不同,SPB 连接的外围设备不需要系统内存地址块来将其寄存器映射到其中。

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

将电源还原到 SPB 连接的外围设备时,驱动程序框架会调用 IPnpCallbackHardware2::OnPrepareHardware 方法,以通知驱动程序必须准备好使用此设备。 在此调用期间,驱动程序接收两个硬件资源列表作为输入参数。 pWdfResourcesRaw 参数指向原始资源列表,pWdfResourcesTranslated 参数指向已翻译的资源列表。 这两个参数都是指向 IWDFCmResourceList 对象的指针。 转换的资源包括 SPB 外围设备驱动程序建立与 SPB 连接的外围设备的逻辑连接所需的 连接 ID 。 有关详细信息,请参阅 SPB 外围设备的连接 ID

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

UmdfDirectHardwareAccess = AllowDirectHardwareAccess 有关此指令的详细信息,请参阅 在 INF 文件中指定 WDF 指令

下面的代码示例演示驱动程序的 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_I2C)
                {
                    if (fConnIdFound == FALSE)
                    {
                        // Save the SPB 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;
    }
}

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

WCHAR szTargetPath[100];
HRESULT hres;

// Create the device path using the well-known resource hub
// path name and the connection ID.
//
// TODO: Replace this hardcoded string with the appropriate
//       helper method from Reshub.h when available.
hres = StringCbPrintfW(&szTargetPath[0],
                       sizeof(szTargetPath),
                       L"\\\\.\\RESOURCE_HUB\\%0*I64x",
                       (size_t)(sizeof(LARGE_INTEGER) * 2),
                       connectionId.QuadPart);
if (FAILED(hres))
{
     // Error handling
     ...
}

前面的代码示例将 SPB 连接的外围设备的路径名称写入数组中 szTargetPath 。 下面的代码示例使用此设备路径名称打开 SPB 连接的外围设备的文件句柄。

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 方法成功,则 SPB 连接的外围设备的驱动程序可以使用 IWDFRemoteTarget 对象向外围设备发送 I/O 请求。 在驱动程序将读取、写入或 IOCTL 请求发送到外围设备之前,驱动程序会调用 IWDFRemoteTarget::FormatRequestForReadIWDFRemoteTarget::FormatRequestForWriteIWDFRemoteTarget::FormatRequestForIoctl 方法来格式化 I/O 请求。 IWDFRemoteTarget 接口从 IWDFIoTarget 接口继承这三种方法。 接下来,驱动程序调用 IWDFIoRequest::Send 方法,将 I/O 请求发送到 SPB 连接的外围设备。

在以下代码示例中,SPB 外围设备驱动程序调用 Send 方法将 IRP_MJ_WRITE 请求发送到 SPB 连接的外围设备。

HRESULT hres;
IWDFMemory *pInputMemory = NULL;
IWDFRemoteTarget *pWdfIoRequest = 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
        ...
    }

    // After this call, the parent holds the only reference.
    pWdfMemory->Release();
}

// Format the I/O request for a write operation.
if (SUCCEEDED(hres))
{
    hres = pRemoteTarget->FormatRequestForWrite(pWdfIoRequest,
                                                NULL,
                                                pInputMemory, 
                                                NULL, 
                                                0);
    if (FAILED(hres))
    {
        // Error handling
        ...
    }
}

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

    // Set the I/O completion callback.
    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 接口的指针,该对象表示 SPB 连接的外围设备。 IWDFDevice::CreateRequest 方法创建 I/O 请求,并将此请求封装在变量指向的 IWDFIoRequest 接口实例中pWdfIoRequest
  2. 变量 pWdfDriver 是指向表示 SPB 外围设备驱动程序的框架驱动程序对象的 IWDFDriver 接口的指针。 pInBufferinBufferSize 变量指定包含写入请求数据的输入缓冲区的地址和大小。 IWDFDriver::CreatePreallocatedWdfMemory 方法为输入缓冲区创建框架内存对象,并将所pWdfIoRequest指向的 IWDFIoRequest 对象指定为内存对象的父对象 (,以便在释放内存对象的父对象) 时自动释放内存对象。 驱动程序调用 Release 方法以释放其对内存对象的本地引用后,父级将保留对此对象的唯一引用。
  3. 变量 pWdfRemoteTarget 是从前面的代码示例中的 OpenFileByName 调用获取的远程目标指针。 IWDFRemoteTarget::FormatRequestForWrite 方法格式化写入操作的 I/O 请求。
  4. 如果要同步发送写入请求,则 fSynchronous 变量为 TRUE ;如果要异步发送,则变量为 FALSE 。 变量 pCallback 是指向以前创建的 IRequestCallbackRequestCompletion 接口的指针。 如果要异步发送请求,则对 IWDFIoRequest::SetCompletionCallback 方法的调用将注册此接口。 稍后,将调用 IRequestCallbackRequestCompletion::OnCompletion 方法,以便在请求异步完成时通知驱动程序。
  5. Send 方法将格式化的写入请求发送到 SPB 连接的外围设备。 变量 Flags 指示是以同步方式还是异步发送写入请求。
  6. 如果请求是同步发送的, 则 IWDFIoRequest::D eleteWdfObject 方法将删除由 pWdfIoRequest 指向的 I/O 请求对象和 由 pInputMemory指向的子对象。 IWDFIoRequest 接口从 IWDFObject 接口继承此方法。 如果请求是异步发送的,则应稍后在驱动程序的 OnCompletion 方法中调用 DeleteWdfObject 方法。

上述代码示例的替代实现可能在驱动程序初始化期间创建 IWDFIoRequestIWDFMemory 对象,并重复使用这些对象,而不是在每次发送 I/O 请求时创建和删除新对象。 有关详细信息,请参阅 IWDFIoRequest2::ReuseIWDFMemory::SetBuffer

此外,如果 发送 调用成功,备用实现可能会检查 I/O 请求中的 I/O 状态代码。 有关详细信息,请参阅 IWDFIoRequest::GetCompletionParams