通用串行總線 (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;
#elsestruct _URB_HEADER UrbHeader;
#endif
#if ..._URB_SELECT_INTERFACE UrbSelectInterface;
#elsestruct _URB_SELECT_INTERFACE UrbSelectInterface;
#endif
#if ..._URB_SELECT_CONFIGURATION UrbSelectConfiguration;
#elsestruct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
#endif
#if ..._URB_PIPE_REQUEST UrbPipeRequest;
#elsestruct _URB_PIPE_REQUEST UrbPipeRequest;
#endif
#if ..._URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl;
#elsestruct _URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl;
#endif
#if ..._URB_GET_FRAME_LENGTH UrbGetFrameLength;
#elsestruct _URB_GET_FRAME_LENGTH UrbGetFrameLength;
#endif
#if ..._URB_SET_FRAME_LENGTH UrbSetFrameLength;
#elsestruct _URB_SET_FRAME_LENGTH UrbSetFrameLength;
#endif
#if ..._URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber;
#elsestruct _URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber;
#endif
#if ..._URB_CONTROL_TRANSFER UrbControlTransfer;
#elsestruct _URB_CONTROL_TRANSFER UrbControlTransfer;
#endif
#if ..._URB_CONTROL_TRANSFER_EX UrbControlTransferEx;
#elsestruct _URB_CONTROL_TRANSFER_EX UrbControlTransferEx;
#endif
#if ..._URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
#elsestruct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
#endif
#if ..._URB_ISOCH_TRANSFER UrbIsochronousTransfer;
#elsestruct _URB_ISOCH_TRANSFER UrbIsochronousTransfer;
#endif
#if ..._URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest;
#elsestruct _URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest;
#endif
#if ..._URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest;
#elsestruct _URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest;
#endif
#if ..._URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest;
#elsestruct _URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest;
#endif
#if ..._URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest;
#elsestruct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest;
#endif
#if ..._URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest;
#elsestruct _URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest;
#endif
#if ..._URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest;
#elsestruct _URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest;
#endif
#if ..._URB_OS_FEATURE_DESCRIPTOR_REQUEST UrbOSFeatureDescriptorRequest;
#elsestruct _URB_OS_FEATURE_DESCRIPTOR_REQUEST UrbOSFeatureDescriptorRequest;
#endif
#if ..._URB_OPEN_STATIC_STREAMS UrbOpenStaticStreams;
#elsestruct _URB_OPEN_STATIC_STREAMS UrbOpenStaticStreams;
#endif
#if ..._URB_GET_ISOCH_PIPE_TRANSFER_PATH_DELAYS UrbGetIsochPipeTransferPathDelays;
#elsestruct _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 IRPIoFreeIrp(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:
//
// NTSTATUSNTSTATUS 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 IRPIoFreeIrp(Irp);Irp = NULL;}return ntStatus;
}