内核模式 SPB 外设驱动程序的硬件资源
本主题中的代码示例演示 内核模式驱动程序框架 如何为 简单外围总线 上的外围设备 (KMDF) 驱动程序 (SPB) 获取运行设备所需的硬件资源。 这些资源中包含驱动程序用来与设备建立逻辑连接的信息。 其他资源可能包括中断以及一个或多个 GPIO 输入或输出引脚。 (GPIO 引脚是配置为输入或输出的通用 I/O 控制器设备上的引脚;有关详细信息,请参阅 常规用途 I/O (GPIO) Drivers.) 与内存映射的设备不同,SPB 连接的外围设备不需要系统内存地址块即可将其寄存器映射到其中。
此驱动程序实现一组即插即用和电源管理事件回调函数。 若要向 KMDF 注册这些函数,驱动程序的 EvtDriverDeviceAdd 事件回调函数调用 WdfDeviceInitSetPnpPowerEventCallbacks 方法。 框架调用电源管理事件回调函数,以通知驱动程序外围设备的电源状态更改。 这些函数中包括 EvtDevicePrepareHardware 函数,该函数执行使设备可供驱动程序访问所需的任何操作。
当电源恢复到外围设备时,驱动程序框架会调用 EvtDevicePrepareHardware 函数,以通知 SPB 外围驱动程序必须准备此设备以供使用。 在此调用期间,驱动程序接收两个硬件资源列表作为输入参数。 ResourcesRaw 参数是原始资源列表的 WDFCMRESLIST 对象句柄,ResourcesTranslated 参数是已翻译资源列表的 WDFCMRESLIST 对象句柄。 已转换的资源包括驱动程序与外围设备建立逻辑连接所需的 连接 ID 。 有关详细信息,请参阅 Connection IDs for SPB-Connected Peripheral Devices(SPB 连接的外围设备的连接 ID)。
下面的代码示例演示 EvtDevicePrepareHardware 函数如何从 ResourcesTranslated 参数获取连接 ID。
BOOLEAN fConnectionIdFound = FALSE;
LARGE_INTEGER connectionId = 0;
ULONG resourceCount;
NTSTATUS status = STATUS_SUCCESS;
resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);
// Loop through the resources and save the relevant ones.
for (ULONG ix = 0; ix < resourceCount; ix++)
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;
pDescriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, ix);
if (pDescriptor == NULL)
{
status = 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 (fConnectionIdFound == FALSE)
{
// Save the SPB connection ID.
connectionId.LowPart = pDescriptor->u.Connection.IdLowPart;
connectionId.HighPart = pDescriptor->u.Connection.IdHighPart;
fConnectionIdFound = TRUE;
}
}
}
if (Class == CM_RESOURCE_CONNECTION_CLASS_GPIO)
{
// Check for GPIO pin resource.
...
}
}
break;
case CmResourceTypeInterrupt:
{
// Check for interrupt resource.
...
}
break;
default:
// Don't care about other resource descriptors.
break;
}
}
前面的代码示例将 SPB 连接的外围设备的连接 ID 复制到名为 的 connectionId
变量中。
下面的代码示例演示如何将此连接 ID 合并到可用于打开与外围设备的逻辑连接的设备路径名称中。 此设备路径名称将资源中心标识为从中获取访问外围设备所需的参数的系统组件。
// Use the connection ID to create the full device path name.
DECLARE_UNICODE_STRING_SIZE(szDeviceName, RESOURCE_HUB_PATH_SIZE);
status = RESOURCE_HUB_CREATE_PATH_FROM_ID(&szDeviceName,
connectionId.LowPart,
connectionId.HighPart);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
在前面的代码示例中,DECLARE_UNICODE_STRING_SIZE宏创建名为 szDeviceName
的已初始化UNICODE_STRING变量的声明,该变量的缓冲区足够大,可以包含资源中心所用格式的设备路径名称。 此宏在 Ntdef.h 头文件中定义。 RESOURCE_HUB_PATH_SIZE常量指定设备路径名称中的字节数。 RESOURCE_HUB_CREATE_PATH_FROM_ID宏从连接 ID 生成设备路径名称。 RESOURCE_HUB_PATH_SIZE 和 RESOURCE_HUB_CREATE_PATH_FROM_ID 在 Reshub.h 头文件中定义。
下面的代码示例使用设备路径名称打开一个文件句柄, (名为 SpbIoTarget
) SPB 连接的外围设备。
// Open the SPB peripheral device as a remote I/O target.
WDF_IO_TARGET_OPEN_PARAMS openParams;
WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&openParams,
&szDeviceName,
(GENERIC_READ | GENERIC_WRITE));
openParams.ShareAccess = 0;
openParams.CreateDisposition = FILE_OPEN;
openParams.FileAttributes = FILE_ATTRIBUTE_NORMAL;
status = WdfIoTargetOpen(SpbIoTarget, &openParams);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
在前面的代码示例中, WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME 函数初始化 WDF_IO_TARGET_OPEN_PARAMS 结构,以便驱动程序可以通过指定设备名称来打开与外围设备的逻辑连接。 变量 SpbIoTarget
是框架 I/O 目标对象的 WDFIOTARGET 句柄。 此句柄是从上一次调用 WdfIoTargetCreate 方法获取的,此示例中未显示。 如果对 WdfIoTargetOpen 方法的调用成功,驱动程序可以使用 句 SpbIoTarget
柄将 I/O 请求发送到外围设备。
在 EvtDriverDeviceAdd 事件回调函数中,SPB 外围驱动程序可以调用 WdfRequestCreate 方法来分配供驱动程序使用的框架请求对象。 稍后,当不再需要 对象时,驱动程序会调用 WdfObjectDelete 方法来删除该对象。 驱动程序可以多次重复使用从 WdfRequestCreate 调用获取的框架请求对象,以将 I/O 请求发送到外围设备。 对于读取、写入或 IOCTL 请求,驱动程序调用 WdfIoTargetSendReadSynchronously、 WdfIoTargetSendWriteSynchronously 或 WdfIoTargetSendIoctlSynchronously 方法来发送请求。
在以下代码示例中,驱动程序调用 WdfIoTargetSendWriteSynchronously 以同步方式将 IRP_MJ_WRITE 请求发送到连接到 SPB 的外围设备。 在此示例开始时, pBuffer
变量指向包含要写入外围设备的数据的非分页缓冲区,变量 dataSize
指定此数据的大小(以字节为单位)。
ULONG_PTR bytesWritten;
NTSTATUS status;
// Describe the input buffer.
WDF_MEMORY_DESCRIPTOR memoryDescriptor;
WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&memoryDescriptor, pBuffer, dataSize);
// Configure the write request to time out after 2 seconds.
WDF_REQUEST_SEND_OPTIONS requestOptions;
WDF_REQUEST_SEND_OPTIONS_INIT(&requestOptions, WDF_REQUEST_SEND_OPTION_TIMEOUT);
requestOptions.Timeout = WDF_REL_TIMEOUT_IN_SEC(2);
// Send the write request synchronously.
status = WdfIoTargetSendWriteSynchronously(SpbIoTarget,
SpbRequest,
&memoryDescriptor,
NULL,
&requestOptions,
&bytesWritten);
if (!NT_SUCCESS(status))
{
// Error handling
...
}
前面的代码示例执行以下操作:
- WDF_MEMORY_DESCRIPTOR_INIT_BUFFER 函数调用初始化
memoryDescriptor
变量,该变量是描述输入缓冲区的WDF_MEMORY_DESCRIPTOR结构。 以前,驱动程序调用 ExAllocatePoolWithTag 等例程来分配非分页池中的缓冲区,并将写入数据复制到此缓冲区。 - WDF_REQUEST_SEND_OPTIONS_INIT函数调用初始化
requestOptions
变量,该变量是一个包含写入请求的可选设置的WDF_REQUEST_SEND_OPTIONS结构。 在此示例中, 结构将请求配置为在两秒后未完成时超时。 - 调用 WdfIoTargetSendWriteSynchronously 方法会将写入请求发送到 SPB 连接的外围设备。 在写入操作完成或超时后,该方法以同步方式返回。如有必要,另一个驱动程序线程可以调用 WdfRequestCancelSentRequest 来取消请求。
在 WdfIoTargetSendWriteSynchronously 调用中,驱动程序提供名为 的 SpbRequest
变量,该变量是驱动程序之前创建的框架请求对象的句柄。 在 WdfIoTargetSendWriteSynchronously 调用后,驱动程序通常应调用 WdfRequestReuse 方法来准备要再次使用的框架请求对象。