通用串行总线 (USB) 客户端驱动程序无法直接与其设备通信。 相反,客户端驱动程序会创建请求并将其提交到 USB 驱动程序堆栈进行处理。 在每个请求中,客户端驱动程序提供一个可变长度的数据结构,称为 USB 请求块 (URB) ,URB 结构描述请求的详细信息,还包含有关已完成请求状态的信息。 客户端驱动程序通过 URL 执行所有特定于设备的操作,包括数据传输。 在将请求提交到 USB 驱动程序堆栈之前,客户端驱动程序必须使用有关请求的信息初始化 URB。 对于某些类型的请求,Microsoft 提供帮助程序例程和宏,这些例程和宏分配 URB 结构,并使用客户端驱动程序提供的详细信息填充 URB 结构的必要成员。
每个 URB 都以标准固定大小的标头开头, (_URB_HEADER) ,其用途是标识请求的操作类型。 _URB_HEADER 的 Length 成员指定 URB 的大小(以字节为单位)。 Function 成员必须是一系列系统定义的URB_FUNCTION_XXX常量之一,用于确定所请求的操作类型。 例如,在数据传输的情况下,此成员指示传输的类型。 函数代码URB_FUNCTION_CONTROL_TRANSFER、URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER和URB_FUNCTION_ISOCH_TRANSFER分别指示控制、批量/中断和常时等量传输。 USB 驱动程序堆栈使用 Status 成员返回特定于 USB 的状态代码。
为了提交 URB,客户端驱动程序使用 IOCTL_INTERNAL_USB_SUBMIT_URB 请求,该请求通过 I/O 请求数据包 (IRP) 类型IRP_MJ_INTERNAL_DEVICE_CONTROL传递到设备。
USB 驱动程序堆栈处理完 URB 后,驱动程序堆栈将使用 URB 结构的 Status 成员返回特定于 USB 的状态代码。
KMDF 和 UMDF 驱动程序开发人员应使用相应的框架接口来与 USB 设备通信。
分配和构建 URB
USB 客户端驱动程序可以使用 Windows 驱动程序模型 (WDM) 驱动程序例程来分配 URB 并格式化 URB,然后再将请求发送到 Microsoft 提供的 USB 驱动程序堆栈。
客户端驱动程序使用 URB 打包 USB 驱动程序堆栈中较低级驱动程序处理请求所需的所有信息。 在 Windows 操作系统中,URB 在 URB 结构中描述。
Microsoft 为 USB 客户端驱动程序提供了例程库。 通过使用这些例程,USB 客户端驱动程序可以为某些指定操作生成 URB 请求,并将其转发到 USB 堆栈中。 如果愿意,可以将客户端驱动程序设计为为支持的操作调用库例程,而不是生成自己的 URB 请求。
Windows 7 及更早版本中的 URB 分配
若要使用适用于 Windows 7 及更早版本的 Windows 的 Windows 驱动程序工具包 (WDK) 中包含的例程发送 USB 请求,客户端驱动程序通常会分配和填充 URB 结构,将 URB 结构与新的 IRP 相关联,并将 IRP 发送到 USB 驱动程序堆栈。
对于某些类型的请求,Microsoft 提供 (由分配 URB 结构的Usbd.sys) 导出的帮助程序例程。 例如, USBD_CreateConfigurationRequestEx 例程为 URB 结构分配内存,为选择配置请求设置 URB 格式,并将 URB 结构的地址返回到客户端驱动程序。 但是,帮助程序例程不能用于所有类型的请求。
Microsoft 还提供了为某些类型的请求设置 URL 格式的宏。 对于这些宏,客户端驱动程序必须通过调用 ExAllocatePoolWithTag 来分配 URB 结构,或者在堆栈上分配 结构。 例如,在客户端驱动程序分配 URB 后,驱动程序可以调用 UsbBuildSelectConfigurationRequest 来格式化选择配置请求的 URB 或清除配置。
对于其他请求,客户端驱动程序必须根据请求类型,通过设置 URB 结构的各个成员来手动分配 和格式化 URB 。
USB 请求完成后,客户端驱动程序必须释放 URB 结构。 如果在堆栈上分配 URB,则 URB 在超出范围时释放。 如果在非分页池中分配 URB,则客户端驱动程序必须调用 ExFreePool 才能释放 URB。
Windows 8中的 URB 分配
WDK for Windows 8 提供了一个新的静态库 Usbdex.lib,用于导出用于分配、格式化和释放 URL 的例程。 此外,还有一种将 URB 与 IRP 相关联的新方法。 新的例程可由面向 Windows Vista 和更高版本的 Windows 的客户端驱动程序调用。
在 Windows Vista 及更高版本上运行的客户端驱动程序必须使用新的例程,以便基础 USB 驱动程序堆栈可以利用某些性能和可靠性改进。 这些改进适用于 Windows 8 中为支持 USB 3.0 设备和主机控制器而引入的新 USB 驱动程序堆栈。 对于 USB 2.0 主控制器,Windows 加载不支持改进的驱动程序堆栈的早期版本。 无论基础驱动程序堆栈的版本或主机控制器支持的协议版本如何,都必须始终调用新的 URB 例程。
在调用任何新例程之前,请确保有一个 USBD 句柄,用于向 USB 驱动程序堆栈注册客户端驱动程序。 若要获取 USBD 句柄, 请调用 USBD_CreateHandle。
WDK 提供以下例程,用于Windows 8。 这些例程在 Usbdlib.h 中定义。
- USBD_UrbAllocate
- USBD_IsochUrbAllocate
- USBD_SelectConfigUrbAllocateAndBuild
- USBD_SelectInterfaceUrbAllocateAndBuild
- USBD_UrbFree
- USBD_AssignUrbToIoStackLocation
前面列表中的分配例程返回指向由 USB 驱动程序堆栈分配的新 URB 结构的指针。 根据 Windows 加载的 USB 驱动程序堆栈的版本, URB 结构可以与不透明的 URB 上下文配对。 URB 上下文是有关 URB 的信息块。 无法查看 URB 标头的内容;这些信息旨在由 USB 驱动程序堆栈在内部使用,以改善 URB 跟踪和处理。 URB 上下文仅由 USB 驱动程序堆栈用于Windows 8。 如果 URB 上下文可用,USB 驱动程序堆栈会使用它使 URB 处理更安全、更高效。 例如,USB 驱动程序堆栈必须确保客户端驱动程序不会提交 URB,然后尝试在第一个请求完成之前重复使用同一 URB。 为了检测此类错误,USB 驱动程序堆栈会将状态信息存储在 URB 上下文中。 如果没有状态信息,USB 驱动程序堆栈将不得不将传入的 URB 与当前正在进行的所有 URB 进行比较。 当客户端驱动程序尝试释放 URB 时,USB 驱动程序堆栈也会使用状态信息。 在释放 URB 之前,USB 驱动程序堆栈会验证状态,以确保 URB 未挂起。
URB 上下文提供用于存储额外 URB 信息的官方机制。 使用 URB 上下文比根据需要分配额外的内存或在 URB 结构的保留成员中存储额外信息更可取。 USB 驱动程序堆栈在非分页池中分配 URB 及其关联的 URB 上下文,以便将来如果需要更大的 URB 上下文,唯一需要调整的是池分配的大小。
URB的结构
typedef struct _URB {
union {
#if ...
_URB_HEADER UrbHeader;
#else
struct _URB_HEADER UrbHeader;
#endif
#if ...
_URB_SELECT_INTERFACE UrbSelectInterface;
#else
struct _URB_SELECT_INTERFACE UrbSelectInterface;
#endif
#if ...
_URB_SELECT_CONFIGURATION UrbSelectConfiguration;
#else
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
#endif
#if ...
_URB_PIPE_REQUEST UrbPipeRequest;
#else
struct _URB_PIPE_REQUEST UrbPipeRequest;
#endif
#if ...
_URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl;
#else
struct _URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl;
#endif
#if ...
_URB_GET_FRAME_LENGTH UrbGetFrameLength;
#else
struct _URB_GET_FRAME_LENGTH UrbGetFrameLength;
#endif
#if ...
_URB_SET_FRAME_LENGTH UrbSetFrameLength;
#else
struct _URB_SET_FRAME_LENGTH UrbSetFrameLength;
#endif
#if ...
_URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber;
#else
struct _URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber;
#endif
#if ...
_URB_CONTROL_TRANSFER UrbControlTransfer;
#else
struct _URB_CONTROL_TRANSFER UrbControlTransfer;
#endif
#if ...
_URB_CONTROL_TRANSFER_EX UrbControlTransferEx;
#else
struct _URB_CONTROL_TRANSFER_EX UrbControlTransferEx;
#endif
#if ...
_URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
#else
struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
#endif
#if ...
_URB_ISOCH_TRANSFER UrbIsochronousTransfer;
#else
struct _URB_ISOCH_TRANSFER UrbIsochronousTransfer;
#endif
#if ...
_URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest;
#else
struct _URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest;
#endif
#if ...
_URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest;
#else
struct _URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest;
#endif
#if ...
_URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest;
#else
struct _URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest;
#endif
#if ...
_URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest;
#else
struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest;
#endif
#if ...
_URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest;
#else
struct _URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest;
#endif
#if ...
_URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest;
#else
struct _URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest;
#endif
#if ...
_URB_OS_FEATURE_DESCRIPTOR_REQUEST UrbOSFeatureDescriptorRequest;
#else
struct _URB_OS_FEATURE_DESCRIPTOR_REQUEST UrbOSFeatureDescriptorRequest;
#endif
#if ...
_URB_OPEN_STATIC_STREAMS UrbOpenStaticStreams;
#else
struct _URB_OPEN_STATIC_STREAMS UrbOpenStaticStreams;
#endif
#if ...
_URB_GET_ISOCH_PIPE_TRANSFER_PATH_DELAYS UrbGetIsochPipeTransferPathDelays;
#else
struct _URB_GET_ISOCH_PIPE_TRANSFER_PATH_DELAYS UrbGetIsochPipeTransferPathDelays;
#endif
};
} URB, *PURB;
URB 例程更新记录
下表汇总了 URB 例程中的更改。
如何提交 URB
客户端驱动程序使用 I/O 控制代码 (IOCTL) IOCTL 与设备通信,这些请求在 I/O 请求数据包 (IRP) 类型为 IRP_MJ_INTERNAL_DEVICE_CONTROL。 对于特定于设备的请求(如选择配置请求),与 IRP 关联的 USB 请求块 (URB) 中介绍了该请求。 将 URB 与 IRP 相关联并将请求发送到 USB 驱动程序堆栈的过程称为提交 URB。 若要提交 URB,客户端驱动程序必须使用 IOCTL_INTERNAL_USB_SUBMIT_URB 作为设备控制代码。 IOCTL 是提供 I/O 接口的“内部”控制代码之一,客户端驱动程序使用该接口来管理其设备和设备连接到的端口。 用户模式应用程序无权访问这些内部 I/O 接口。
先决条件
在将请求发送到通用串行总线 (USB) 驱动程序堆栈之前,客户端驱动程序必须根据请求的类型分配 URB 结构和格式,如下步骤:
- 通过调用 IoAllocateIrp 例程为 URB 分配 IRP。 必须提供接收 IRP 的设备对象的堆栈大小。 在对 IoAttachDeviceToDeviceStack 例程的上一次调用中,你收到了指向该设备对象的指针。 堆栈大小存储在 DEVICE_OBJECT 结构的 StackSize 成员中;
- 通过调用 IoGetNextIrpStackLocation 获取指向 IRP 的第一个堆栈位置 (IO_STACK_LOCATION) 的指针;
- 将 IO_STACK_LOCATION 结构的 MajorFunction 成员设置为IRP_MJ_INTERNAL_DEVICE_CONTROL;
- 将 IO_STACK_LOCATION 结构的 Parameters.DeviceIoControl.IoControlCode 成员设置为IOCTL_INTERNAL_USB_SUBMIT_URB;
- 将 IO_STACK_LOCATION 结构的 Parameters.Others.Argument1 成员设置为初始化的 URB 结构的地址。 若要将 IRP 关联到 URB,也可以仅当 URB 由 USBD_UrbAllocate、USBD_SelectConfigUrbAllocateAndBuild 或 USBD_SelectInterfaceUrbAllocateAndBuild 分配时才调用 USBD_AssignUrbToIoStackLocation;
- 通过调用 IoSetCompletionRoutineEx 设置完成例程。如果异步提交 URB,请传递指向调用方实现的完成例程及其上下文的指针。 调用方在其完成例程中释放 IRP。如果要同步提交 IRP,请实现一个完成例程,并在调用 IoSetCompletionRoutineEx 时传递指向该例程的指针。 调用还需要 Context 参数中的初始化 KEVENT 对象。 在完成例程中,将 事件设置为信号状态;
- 调用 IoCallDriver 将填充的 IRP 转发到设备堆栈中的下一个下一个设备对象。 对于同步调用,在调用 IoCallDriver 后,通过调用 KeWaitForSingleObject 来等待事件对象以获取事件通知;
- 完成 IRP 后,检查 IRP 的 IoStatus.Status 成员并评估结果。 如果 IoStatus.Status STATUS_SUCCESS,则表示请求成功;
USB 同步提交
以下示例演示如何同步提交 URB。
// The SubmitUrbSync routine submits an URB synchronously.
//
// Parameters:
// DeviceExtension: Pointer to the caller's device extension. The
// device extension must have a pointer to
// the next lower device object in the device stacks.
//
// Irp: Pointer to an IRP allocated by the caller.
//
// Urb: Pointer to an URB that is allocated by USBD_UrbAllocate,
// USBD_IsochUrbAllocate, USBD_SelectConfigUrbAllocateAndBuild,
// or USBD_SelectInterfaceUrbAllocateAndBuild.
// CompletionRoutine: Completion routine.
//
// Return Value:
//
// NTSTATUS
NTSTATUS SubmitUrbSync( PDEVICE_EXTENSION DeviceExtension,
PIRP Irp,
PURB Urb,
PIO_COMPLETION_ROUTINE SyncCompletionRoutine)
{
NTSTATUS ntStatus;
KEVENT kEvent;
PIO_STACK_LOCATION nextStack;
// Get the next stack location.
nextStack = IoGetNextIrpStackLocation(Irp);
// Set the major code.
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
// Set the IOCTL code for URB submission.
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
// Attach the URB to this IRP.
// The URB must be allocated by USBD_UrbAllocate, USBD_IsochUrbAllocate,
// USBD_SelectConfigUrbAllocateAndBuild, or USBD_SelectInterfaceUrbAllocateAndBuild.
USBD_AssignUrbToIoStackLocation (DeviceExtension->UsbdHandle, nextStack, Urb);
KeInitializeEvent(&kEvent, NotificationEvent, FALSE);
ntStatus = IoSetCompletionRoutineEx ( DeviceExtension->NextDeviceObject,
Irp,
SyncCompletionRoutine,
(PVOID) &kEvent,
TRUE,
TRUE,
TRUE);
if (!NT_SUCCESS(ntStatus))
{
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "IoSetCompletionRoutineEx failed. \n" ));
goto Exit;
}
ntStatus = IoCallDriver(DeviceExtension->NextDeviceObject, Irp);
if (ntStatus == STATUS_PENDING)
{
KeWaitForSingleObject ( &kEvent,
Executive,
KernelMode,
FALSE,
NULL);
}
ntStatus = Irp->IoStatus.Status;
Exit:
if (!NT_SUCCESS(ntStatus))
{
// We hit a failure condition,
// We will free the IRP
IoFreeIrp(Irp);
Irp = NULL;
}
return ntStatus;
}
// The SyncCompletionRoutine routine is the completion routine
// for the synchronous URB submit request.
//
// Parameters:
//
// DeviceObject: Pointer to the device object.
// Irp: Pointer to an I/O Request Packet.
// CompletionContext: Context for the completion routine.
//
// Return Value:
//
// NTSTATUS
NTSTATUS SyncCompletionRoutine ( PDEVICE_OBJECT DeviceObject,
PIRP Irp,
PVOID Context)
{
PKEVENT kevent;
kevent = (PKEVENT) Context;
if (Irp->PendingReturned == TRUE)
{
KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
}
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Request completed. \n" ));
return STATUS_MORE_PROCESSING_REQUIRED;
}
USB 异步提交
以下示例演示如何异步提交 URB。
// The SubmitUrbASync routine submits an URB asynchronously.
//
// Parameters:
//
// Parameters:
// DeviceExtension: Pointer to the caller's device extension. The
// device extension must have a pointer to
// the next lower device object in the device stacks.
//
// Irp: Pointer to an IRP allocated by the caller.
//
// Urb: Pointer to an URB that is allocated by USBD_UrbAllocate,
// USBD_IsochUrbAllocate, USBD_SelectConfigUrbAllocateAndBuild,
// or USBD_SelectInterfaceUrbAllocateAndBuild.
// CompletionRoutine: Completion routine.
//
// CompletionContext: Context for the completion routine.
//
//
// Return Value:
//
// NTSTATUS
NTSTATUS SubmitUrbASync ( PDEVICE_EXTENSION DeviceExtension,
PIRP Irp,
PURB Urb,
PIO_COMPLETION_ROUTINE CompletionRoutine,
PVOID CompletionContext)
{
// Completion routine is required if the URB is submitted asynchronously.
// The caller's completion routine releases the IRP when it completes.
NTSTATUS ntStatus = -1;
PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(Irp);
// Attach the URB to this IRP.
nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
// Attach the URB to this IRP.
nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;
// Attach the URB to this IRP.
(void) USBD_AssignUrbToIoStackLocation (DeviceExtension->UsbdHandle, nextStack, Urb);
// Caller's completion routine will free the irp when it completes.
ntStatus = IoSetCompletionRoutineEx ( DeviceExtension->NextDeviceObject,
Irp,
CompletionRoutine,
CompletionContext,
TRUE,
TRUE,
TRUE);
if (!NT_SUCCESS(ntStatus))
{
goto Exit;
}
(void) IoCallDriver(DeviceExtension->NextDeviceObject, Irp);
Exit:
if (!NT_SUCCESS(ntStatus))
{
// We hit a failure condition,
// We will free the IRP
IoFreeIrp(Irp);
Irp = NULL;
}
return ntStatus;
}