비고
이 문서는 디바이스 드라이버 개발자를 위한 것입니다. 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)로 전환하라는 요청을 받습니다. 이 요청을 받으면 버스 드라이버는 보류 중인 유휴 요청 IRP를 STATUS_SUCCESS 상태로 처리합니다.
유휴 요청 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;부모 드라이버에 유휴 요청을 보냅니다.
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 전원 상태를 요청했음을 나타냅니다. 이 요청이 발생하면 버스 드라이버는 보류 중인 모든 유휴 IRP를 STATUS_POWER_STATE_INVALID로 완료합니다. |
| 장치 사용 중 | 버스 드라이버가 디바이스에 대해 보류 중인 유휴 요청 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를 취소하고 디바이스가 더 낮은 전원 상태로 이동하도록 준비합니다.
- PowerState 매개 변수가 열거자 값 PowerDeviceD2(wdm.h; ntddk.h에 정의됨)로 설정된 PoRequestPowerIrp를 호출하여 디바이스를 WDM 절전 모드로 설정합니다.
허브 드라이버와 USBUsbccgp.sys(일반 부모 드라이버) 는 모두 IRQL = PASSIVE_LEVEL 유휴 알림 콜백 루틴을 호출합니다. 그러면 전원 상태 변경 요청이 완료되기를 기다리는 동안 콜백 루틴을 차단할 수 있습니다.
콜백 루틴은 시스템이 S0 에 있고 디바이스가 D0에 있는 동안에만 호출됩니다.
유휴 요청 알림 콜백 루틴에는 다음과 같은 제한 사항이 적용됩니다.
- 디바이스 드라이버는 유휴 알림 콜백 루틴에서 D0 에서 D2 로 디바이스 전원 상태 전환을 시작할 수 있지만 다른 전원 상태 전환은 허용되지 않습니다. 특히 드라이버는 콜백 루틴을 실행하는 동안 디바이스를 D0 으로 변경하려고 시도해서는 안됩니다.
- 디바이스 드라이버는 유휴 알림 콜백 루틴 내에서 둘 이상의 전원 IRP를 요청해서는 안됩니다.
유휴 상태 알림 콜백 루틴에서 장치 활성화 위한 준비
유휴 알림 콜백 루틴은 디바이스에 보류 중인 IRP_MN_WAIT_WAKE 요청이 있는지 여부를 결정해야 합니다. 보류 중인 IRP_MN_WAIT_WAKE 요청이 없는 경우 콜백 루틴은 디바이스를 일시 중단하기 전에 IRP_MN_WAIT_WAKE 요청을 제출해야 합니다. 대기 절전 모드 해제 메커니즘에 대한 자세한 내용은 WakeUp 기능이 있는 지원 디바이스를 참조하세요.
USB 전역 일시 중단
USB 2.0 규격에서는 USB 호스트 컨트롤러 뒤쪽의 전체 버스에서 시작 프레임 패킷을 포함하여 모든 USB 트래픽을 중지시키는 것을 글로벌 서스펜드로 정의합니다. 아직 일시 중단되지 않은 다운스트림 디바이스는 업스트림 포트에서 유휴 상태를 검색하고 자체에서 일시 중단 상태를 입력합니다. Windows는 이러한 방식으로 전역 일시 중단을 구현하지 않습니다. Windows는 항상 버스의 모든 USB 트래픽을 중단하기 전에 USB 호스트 컨트롤러 뒤에 있는 각 USB 디바이스를 선택적으로 일시 중단합니다.
전역 일시 중단 조건
USB 허브 드라이버는 연결된 모든 디바이스가 D1, D2 또는 D3 디바이스 전원 상태인 모든 허브를 선택적으로 일시 중단합니다. 모든 USB 허브가 선택적으로 일시 중단되면 전체 버스가 전체 일시 중단 모드에 들어갑니다. USB 드라이버 스택은 디바이스가 D1, D2 또는 D3의 WDM 디바이스 상태에 있을 때마다 디바이스를 유휴 상태로 처리합니다.