USB 選擇性暫停
注意
本文適用於設備驅動器開發人員。 如果您遇到 USB 裝置的困難,請參閱 修正 Windows 中的 USB-C 問題
USB 選擇性暫停功能可讓中樞驅動程式暫停個別埠,而不會影響中樞上其他埠的作業。 選擇性暫停 USB 裝置在可攜式電腦中特別有用,因為它有助於節省電池電力。 許多裝置,如指紋讀取器和其他類型的生物特徵辨識掃描器,只需要間歇性電源。 當裝置未使用時暫停這類裝置,可降低整體耗電量。 更重要的是,任何沒有選擇性暫停的裝置都可能導致 USB 主機控制器停用其傳輸排程,而該排程位於系統記憶體中。 主機控制器的直接記憶體存取 (DMA) 傳輸至排程器,可以防止系統的處理器進入更深層的睡眠狀態,例如 C3。
有兩種不同的機制可以選擇性地暫停 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 作業系統的版本會決定非複合裝置驅動程式啟用選擇性暫停的方式。
- Windows XP:在 Windows XP 上,所有用戶端驅動程式都必須使用閑置要求 IRP(IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION)來關閉其裝置。 用戶端驅動程式不得使用 WDM 電源 IRP 選擇性地暫停其裝置。 這麼做可防止其他裝置選擇性地暫停。
- Windows Vista 和更新版本的 Windows:驅動程式寫入器有更多選項可關閉 Windows Vista 中的裝置,以及更新版本的 Windows。 雖然 Windows Vista 支援 Windows 閑置要求 IRP 機制,但驅動程式不需要使用它。
下表顯示需要使用閑置要求 IRP 的案例,以及可以使用 WDM 電源 IRP 暫停 USB 裝置的案例:
Windows 版本 | 複合裝置上的功能,用於喚醒 | 複合裝置上的功能,未針對喚醒進行武裝 | 單一介面 USB 裝置 |
---|---|---|---|
Windows 7 | 使用閑置要求 IRP | 使用WDM電源 IRP | 使用WDM電源 IRP |
Windows Server 2008 | 使用閑置要求 IRP | 使用WDM電源 IRP | 使用WDM電源 IRP |
Windows Vista | 使用閑置要求 IRP | 使用WDM電源 IRP | 使用WDM電源 IRP |
Windows Server 2003 | 使用閑置要求 IRP | 使用閑置要求 IRP | 使用閑置要求 IRP |
Windows XP | 使用閑置要求 IRP | 使用閑置要求 IRP | 使用閑置要求 IRP |
本節說明 Windows 選擇性暫停機制。
傳送USB閑置要求IRP
當裝置閑置時,客戶端驅動程式會傳送閑置要求 IRP 來通知總線驅動程式(IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION)。 在總線驅動程式判斷裝置處於低功率狀態是安全的之後,它會呼叫用戶端設備驅動器透過閑置要求 IRP 將堆疊傳遞下來的回呼例程。
在回呼例程中,用戶端驅動程序必須取消所有擱置的 I/O 作業,並等候所有 USB I/O IRP 完成。 然後,它可以發出 IRP_MN_SET_POWER 要求,將WDM裝置電源狀態變更為 D2。 回呼例程必須在傳回之前等待 D2 要求完成。 如需閑置通知回呼例程的詳細資訊,請參閱
在呼叫閑置通知回呼例程之後,總線驅動程式不會完成閑置要求 IRP。 相反地,總線驅動程式會保留閑置要求 IRP 擱置,直到下列其中一個條件成立為止:
- 收到 IRP_MN_SUPRISE_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 產生關聯。 如需閑置通知完成例程和範例程式代碼的詳細資訊,請參閱
。 IoSetCompletionRoutine (irp, IdleNotificationRequestComplete, DeviceContext, TRUE, TRUE, TRUE);
將閑置要求儲存在裝置擴充功能中。
deviceExtension->PendingIdleIrp = irp;
將Idle要求傳送至父驅動程式。
ntStatus = IoCallDriver (DeviceContext->TopOfStackDeviceObject, irp);
取消USB閑置要求
在某些情況下,設備驅動器可能需要取消已提交至總線驅動程序的閑置要求 IRP。 如果移除裝置、在閑置後變成作用中並傳送閑置要求,或整個系統正在轉換為較低的系統電源狀態,就可能會發生這種情況。
用戶端驅動程式會呼叫 IoCancelIrp來取消閑置的IRP。 下表說明取消閑置 IRP 的三個案例,並指定驅動程式必須採取的動作:
案例 | 閑置要求取消機制 |
---|---|
用戶端驅動程式已取消閑置的 IRP,且 USB 驅動程式堆疊未呼叫「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 封鎖可能會導致死結。
下列清單指出閑置要求完成例程應該如何解譯一些常見的狀態代碼:
狀態碼 | 描述 |
---|---|
STATUS_SUCCESS | 表示裝置不應再暫停。 不過,驅動程式應該確認其裝置已開機,並在 D0 中尚未使用 D0 時將它們放在 D0 中。 |
STATUS_CANCELLED | 在下列任一情況下,總線驅動程式會以STATUS_CANCELLED完成閑置要求 IRP:
|
STATUS_POWER_STATE_INVALID | 表示裝置驅動程式要求其裝置的 D3 電源狀態。 發生這種情況時,總線驅動程式會使用 STATUS_POWER_STATE_INVALID完成所有擱置的閑置 IRP。 |
STATUS_DEVICE_BUSY | 表示總線驅動程式已保留裝置擱置的閑置要求 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 中)。 在 Windows XP 中,驅動程式不得將其裝置放在 PowerDeviceD3 中,即使裝置未提供遠端喚醒武裝也一樣。
在 Windows XP 中,驅動程式必須依賴閑置通知回呼例程,選擇性地暫停裝置。 如果 Windows XP 中執行的驅動程式直接讓裝置處於較低的電源狀態,而不需使用閑置通知回呼例程,這可能會防止 USB 裝置樹狀結構中的其他裝置暫停。
中樞驅動程式和 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 裝置。
Windows 7 中全域暫停的條件
Windows 7 對於選擇性暫停 USB 中樞比 Windows Vista 更積極。 Windows 7 USB 中樞驅動程式會選擇性地暫停其所有連結裝置都處於 D1、 D2 或 D3 裝置電源狀態的任何中樞。 一旦所有 USB 中樞都選擇性暫停,整個總線就會進入全域暫停。 每當裝置處於 D1、D2 或 D3 的 WDM 裝置狀態時,Windows 7 USB 驅動程式堆疊會將裝置視為閑置。
Windows Vista 中全域暫停的條件
在 Windows Vista 中執行全域暫停的需求比 Windows XP 更有彈性。
特別是,每當裝置處於 D1、D2 或 D3 的 WDM 裝置狀態時,USB 堆疊會將裝置視為 Windows Vista 中的閑置。
下圖說明 Windows Vista 中可能發生的案例。
此圖說明類似
在 Windows Vista 上,所有非中樞 USB 裝置都必須在 D1、D2 或 D3 中,才會起始全域暫停,此時所有 USB 中樞,包括根中樞在內的所有 USB 中樞都會暫停。 這表示任何不支援選擇性暫停的USB用戶端驅動程式,可防止總線進入全域暫停。
Windows XP 中全域暫停的條件
為了將 Windows XP 的省電最大化,每個裝置驅動程式都使用閑置要求 IRP 來暫停其裝置非常重要。 如果某個驅動程式以 IRP_MN_SET_POWER 要求暫停其裝置,而不是閑置要求 IRP,它可能會防止其他裝置暫停。
下圖說明 Windows XP 中可能發生的案例。
在此圖中,裝置 3 處於電源狀態 D3,而且沒有擱置的閑置要求 IRP。 裝置 3 不符合 Windows XP 中全域暫停之目的的閒置裝置資格,因為它沒有與其父系擱置的閒置要求 IRP。 這可防止總線驅動程式呼叫與樹狀結構中其他裝置驅動程式相關聯的閑置要求回呼例程。
啟用選擇性暫停
已停用選擇性暫停,以升級Microsoft Windows XP 版本。 它已啟用 Windows XP、Windows Vista 和更新版本的 Windows 全新安裝。
若要啟用指定根中樞及其子裝置的選擇性暫停支援,請選取 裝置管理員 中 USB 根中樞的 [電源管理] 索引卷標上的複選框。
或者,您可以在 USB 埠驅動程式的軟體金鑰下設定 HcDisableSelectiveSuspend 值,以啟用或停用選擇性暫停。 值為 1 會停用選擇性暫停。 值為 0 可啟用選擇性暫停。
例如,Usbport.inf 中的下列幾行會停用 Hydra OHCI 控制器的選擇性暫停:
[OHCI_NOSS.AddReg.NT]
HKR,,"HcDisableSelectiveSuspend",0x00010001,1
用戶端驅動程式不應該嘗試判斷是否在傳送閑置要求之前啟用選擇性暫停。 每當裝置閑置時,他們都應該提交閑置要求。 如果閑置要求失敗,客戶端驅動程序應該重設閑置定時器並重試。