備註
本文適用於設備驅動器開發人員。 如果您遇到 USB 裝置的困難,請參閱 修正 Windows 中的 USB-C 問題
USB 選擇性暫停功能可讓中樞驅動程式暫停個別埠,而不會影響中樞上其他埠的作業。 此功能在可攜式計算機中很有用,因為它有助於節省電池電力。 許多裝置,例如生物特徵辨識掃描器,只需要間歇性電源。 當這類裝置未使用時,暫停這類裝置會降低整體耗電量。 更重要的是,任何沒有被選擇性暫停的裝置可能會阻止 USB 主機控制器停用其位於系統記憶體中的傳輸計劃。 主機控制器的直接記憶體存取 (DMA) 傳輸至排程器,可以防止系統的處理器進入更深層的睡眠狀態,例如 C3。
選擇性暫停預設為啟用。 Microsoft強烈建議不要停用選擇性暫停。
用戶端驅動程式不應該嘗試判斷是否在傳送閑置要求之前啟用選擇性暫停。 每當裝置閑置時,他們都應該提交閑置要求。 如果閑置要求失敗,客戶端驅動程序應該重設閑置定時器並重試。
若要選擇性地暫停 USB 裝置,存在兩種不同的機制:閑置要求 IRP(IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION)和設定電源 IRP(IRP_MN_SET_POWER)。 要使用的機制取決於裝置類型:複合或非複合。
選取選擇性暫停機制
複合裝置介面上可啟用遠端喚醒功能的用戶端驅動程式,使用等候喚醒 IRP(IRP_MN_WAIT_WAKE),必須利用閒置要求 IRP (IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION)機制來選擇性地暫停裝置。
如需遠端喚醒的相關信息,請參閱:
本節說明 Windows 選擇性暫停機制。
傳送USB閑置要求IRP
當裝置閑置時,客戶端驅動程式會傳送閑置要求 IRP 來通知總線驅動程式(IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION)。 在總線驅動程式判斷裝置處於低功率狀態是安全的之後,它會呼叫用戶端設備驅動器透過閑置要求 IRP 將堆疊傳遞下來的回呼例程。
在回呼例程中,用戶端驅動程序必須取消所有擱置的 I/O 作業,並等候所有 USB I/O IRP 完成。 然後,它可以發出 IRP_MN_SET_POWER 要求,將WDM裝置電源狀態變更為 D2。 回呼例程必須在傳回之前等待 D2 要求完成。 如需閑置通知回呼例程的詳細資訊,請參閱 實作USB閑置要求IRP回呼例程。
在呼叫閑置通知回呼例程之後,總線驅動程式不會完成閑置要求 IRP。 相反地,公車司機會保留閑置要求 IRP 擱置,直到下列其中一個條件成立為止:
- 收到 IRP_MN_SURPRISE_REMOVAL 或 IRP_MN_REMOVE_DEVICE IRP 。 收到其中一個 IRP 時,閒置請求 IRP 會完成 STATUS_CANCELLED。
- 總線驅動程式會收到將裝置置於工作電源狀態 (D0) 的要求。 當接收到此請求時,巴士司機會使用 STATUS_SUCCESS 完成等待處理的閒置請求 IRP。
下列限制適用於使用閒置請求的 IRP:
- 傳送閑置要求 IRP 時,驅動程式必須處於裝置電源狀態 D0 。
- 驅動程式每個裝置堆疊只能傳送一個閑置要求 IRP。
下列 WDM 範例程式代碼說明設備驅動器傳送 USB 閑置要求 IRP 所採取的步驟。 下列程式代碼範例會省略錯誤檢查。
配置和初始化 IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION IRP
irp = IoAllocateIrp (DeviceContext->TopOfStackDeviceObject->StackSize, FALSE); nextStack = IoGetNextIrpStackLocation (irp); nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION; nextStack->Parameters.DeviceIoControl.InputBufferLength = sizeof(struct _USB_IDLE_CALLBACK_INFO);分配和初始化閒置請求資訊結構(USB_IDLE_CALLBACK_INFO)。
idleCallbackInfo = ExAllocatePool (NonPagedPool, sizeof(struct _USB_IDLE_CALLBACK_INFO)); idleCallbackInfo->IdleCallback = IdleNotificationCallback; // Put a pointer to the device extension in member IdleContext idleCallbackInfo->IdleContext = (PVOID) DeviceExtension; nextStack->Parameters.DeviceIoControl.Type3InputBuffer = idleCallbackInfo;設定完成例程。
用戶端驅動程序必須將完成例程與閑置要求 IRP 產生關聯。 如需閑置通知完成例程和範例程式代碼的詳細資訊,請參閱 實作USB閑置要求IRP完成例程。
IoSetCompletionRoutine (irp, IdleNotificationRequestComplete, DeviceContext, TRUE, TRUE, TRUE);將閑置要求儲存在裝置擴充功能中。
deviceExtension->PendingIdleIrp = irp;將Idle要求傳送至父驅動程式。
ntStatus = IoCallDriver (DeviceContext->TopOfStackDeviceObject, irp);
取消USB閑置要求
在某些情況下,設備驅動器可能需要取消提交至總線驅動程序的閑置要求 IRP。 如果裝置被移除、在閒置後重新變成作用中並送出閒置請求,或整個系統正在轉換到較低的系統電源狀態,就可能發生這種情況。
用戶端驅動程式會呼叫 IoCancelIrp來取消閑置的IRP。 下表說明取消閑置 IRP 的三個案例,並指定驅動程式必須採取的動作:
| 情境 | 閑置要求取消機制 |
|---|---|
| 客戶端驅動程式會取消空閒 IRP,而 USB 空閒狀態通知回呼程序尚未執行。 | USB 驅動程式堆疊會完成閒置的 IRP。 因為裝置永遠不會離開 D0,所以驅動程式不會變更裝置狀態。 |
| 用戶端驅動程式會取消閑置 IRP,USB 驅動程式堆疊會呼叫 USB 閑置通知回呼例程,而且尚未傳回。 | 即使客戶端驅動程式已經取消了 IRP,仍有可能會觸發 USB 閒置通知回調例程。 在此情況下,客戶端驅動程式的回呼例程仍必須同步將裝置轉換到較低的電源狀態以關閉電源。 當裝置處於較低電源狀態時,客戶端驅動程式就可以傳送 D0 要求。 或者,驅動程式可以等候 USB 驅動程式堆疊完成閒置的 IRP,然後再傳送D0 IRP。 如果回呼例程因為記憶體不足而無法讓裝置進入低電源狀態,因為無法配置電源 IRP,它應該取消閒置的 IRP 並立即退出。 在回呼例程返回之前,閒置 IRP 不會完成。 因此,回呼例程不應該封鎖等候取消的閑置 IRP 完成。 |
| 裝置已處於低電源狀態。 | 如果裝置已處於低電源狀態,則用戶端驅動程式可以傳送 D0 IRP。 USB 驅動程式堆疊以 STATUS_SUCCESS 完成閒置要求 IRP。 或者,驅動程式可以取消閑置的 IRP,等候 USB 驅動程式堆疊完成閑置的 IRP,然後傳送 D0 IRP。 |
USB 閑置要求 IRP 完成例程
在許多情況下,總線驅動程式可能會呼叫驅動程序的閑置要求 IRP 完成例程。 如果發生這種情況,用戶端驅動程式必須偵測總線驅動程式完成 IRP 的原因。 傳回的狀態代碼可以提供這項資訊。 如果狀態代碼不是 STATUS_POWER_STATE_INVALID,且裝置尚未在 D0 中,驅動程式應將其裝置放在 D0 中。 如果裝置仍然閑置,驅動程式可以提交另一個閑置要求 IRP。
備註
閒置要求 IRP 完成例行程序不應阻塞以等待 D0 電源要求的完成。 完成例程可以在中樞驅動程式的電源 IRP 內容中呼叫,而完成例程中的另一個電源 IRP 封鎖可能會導致死結。
下列清單指出閑置要求完成例程應該如何解譯一些常見的狀態代碼:
| 狀態代碼 | 說明 |
|---|---|
| 狀態_成功 | 表示裝置不應再暫停。 不過,驅動程式應該確認其裝置已開機;如果尚未處於D0中,應將它們放入D0中。 |
| 狀態_已取消 | 在下列任一情況下,總線驅動程式會以STATUS_CANCELLED完成閑置要求 IRP:
|
| 狀態_電源_狀態_無效 | 表示裝置驅動程式要求其裝置的 D3 電源狀態。 發生此要求時,總線驅動程式會以STATUS_POWER_STATE_INVALID完成所有擱置的閑置 IRP。 |
| 設備狀態繁忙 | 表示總線驅動程式已保留裝置擱置的閑置要求 IRP。 指定的裝置一次只能有一個閒置 IRP 可等待處理。 提交多個閒置請求 IRP 是電源管理策略所有者的錯誤。 驅動程式寫入器會解決錯誤。 |
下列程式代碼範例示範閑置要求完成例程的範例實作。
/*
Routine Description:
Completion routine for idle notification IRP
Arguments:
DeviceObject - pointer to device object
Irp - I/O request packet
DeviceExtension - pointer to device extension
Return Value:
NT status value
--*/
NTSTATUS
IdleNotificationRequestComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PDEVICE_EXTENSION DeviceExtension
)
{
NTSTATUS ntStatus;
POWER_STATE powerState;
PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;
ntStatus = Irp->IoStatus.Status;
if(!NT_SUCCESS(ntStatus) && ntStatus != STATUS_NOT_SUPPORTED)
{
//Idle IRP completes with error.
switch(ntStatus)
{
case STATUS_INVALID_DEVICE_REQUEST:
//Invalid request.
break;
case STATUS_CANCELLED:
//1. The device driver canceled the IRP.
//2. A system power state change is required.
break;
case STATUS_POWER_STATE_INVALID:
// Device driver requested a D3 power state for its device
// Release the allocated resources.
goto IdleNotificationRequestComplete_Exit;
case STATUS_DEVICE_BUSY:
//The bus driver already holds an idle IRP pending for the device.
break;
default:
break;
}
// If IRP completes with error, issue a SetD0
//Increment the I/O count because
//a new IRP is dispatched for the driver.
//This call is not shown.
powerState.DeviceState = PowerDeviceD0;
// Issue a new IRP
PoRequestPowerIrp (
DeviceExtension->PhysicalDeviceObject,
IRP_MN_SET_POWER,
powerState,
(PREQUEST_POWER_COMPLETE) PoIrpCompletionFunc,
DeviceExtension,
NULL);
}
IdleNotificationRequestComplete_Exit:
idleCallbackInfo = DeviceExtension->IdleCallbackInfo;
DeviceExtension->IdleCallbackInfo = NULL;
DeviceExtension->PendingIdleIrp = NULL;
InterlockedExchange(&DeviceExtension->IdleReqPend, 0);
if(idleCallbackInfo)
{
ExFreePool(idleCallbackInfo);
}
DeviceExtension->IdleState = IdleComplete;
// Because the IRP was created using IoAllocateIrp,
// the IRP needs to be released by calling IoFreeIrp.
// Also return STATUS_MORE_PROCESSING_REQUIRED so that
// the kernel does not reference this.
IoFreeIrp(Irp);
KeSetEvent(&DeviceExtension->IdleIrpCompleteEvent, IO_NO_INCREMENT, FALSE);
return STATUS_MORE_PROCESSING_REQUIRED;
}
USB 閒置通知回呼函數
總線驅動程式(可以是中樞驅動程式的實例或泛型父驅動程式)負責判斷何時能安全地暫停其裝置中的子系統。 如果是,它會呼叫由每個子用戶端驅動程式提供的閒置狀態通知回調例程。
USB_IDLE_CALLBACK的函式原型如下所示:
typedef VOID (*USB_IDLE_CALLBACK)(__in PVOID Context);
設備驅動器必須在閑置通知回呼例程中採取下列動作:
- 如果裝置必須啟用遠端喚醒,請要求該裝置的 IRP_MN_WAIT_WAKE IRP。
- 取消所有 I/O,並準備裝置以進入較低的電源狀態。
- 藉由呼叫 PoRequestPowerIrp 並將 PowerState 參數設定為列舉值 PowerDeviceD2 ,將裝置置於 WDM 睡眠狀態(定義於 wdm.h; ntddk.h 中)。
中樞驅動程式和 USB 泛型父驅動程式 (Usbccgp.sys) 都會在 IRQL = PASSIVE_LEVEL 呼叫閑置通知回呼例程。 回呼例程接著可以在等候電源狀態變更要求完成時封鎖。
只有在系統位於 S0 且裝置位於 D0 時,才會叫用回呼例程。
下列限制適用於閒置請求通知回呼例程:
- 設備驅動器可以在閑置通知回呼例程中起始從 D0 到 D2 的裝置電源狀態轉換,但不允許其他電源狀態轉換。 特別是,驅動程式在執行回呼例程時,不得嘗試將其裝置變更為 D0 。
- 設備驅動器不得在閑置通知回調例程內要求多個電源 IRP。
在閒置通知回呼例程中將裝置設置為喚醒模式
閒置通知回呼例程應該判斷其裝置是否有 IRP_MN_WAIT_WAKE 待處理要求。 如果沒有正在等待的 IRP_MN_WAIT_WAKE 請求,回呼例程應該在暫停裝置之前先提交一個 IRP_MN_WAIT_WAKE 請求。 如需等候喚醒機制的詳細資訊,請參閱 支援具有喚醒功能的裝置。
USB 全域暫停
USB 2.0 規格會將全域暫停定義為USB主機控制器後面的整個總線暫停,方法是停止總線上的所有USB流量,包括啟動框架封包。 尚未暫停的下游裝置會偵測其上游埠上的閑置狀態,並自行進入暫停狀態。 Windows 不會以這種方式實作全域暫停。 在停止總線上所有 USB 流量之前,Windows 一律會選擇性地暫停 USB 主機控制器後方的每個 USB 裝置。
全域暫停的條件
USB 集線器驅動程式會在其所有連結裝置處於 D1、D2 或 D3 裝置電源狀態時,選擇性地暫停該中樞。 在所有 USB 中樞被選擇性暫停之後,整個總線便會進入全域暫停。 每當裝置處於 D1、 D2 或 D3 的 WDM 裝置狀態時,USB 驅動程式堆疊會將裝置視為閒置。