USB 파이프 오류에서 복구하는 방법
참고 항목
이 문서는 디바이스 드라이버 개발자를 위한 것입니다. USB 디바이스에 어려움이 있는 경우 Windows에서 USB-C 문제 해결을 참조 하세요.
이 문서에서는 USB 파이프로의 데이터 전송이 실패할 때 시도할 수 있는 단계에 대한 정보를 제공합니다. 이 문서에 설명된 메커니즘은 대량, 인터럽트 및 등시 파이프에 대한 포트 중단, 다시 설정 및 주기 포트 작업을 다룹니다.
USB 클라이언트 드라이버는 기본 엔드포인트로 제어 전송을 전송하여 디바이스와 통신합니다. 디바이스의 대량, 인터럽트 및 등시 엔드포인트로 데이터 전송 때때로 이러한 전송은 엔드포인트의 중단 조건과 같은 다양한 이유로 인해 실패할 수 있습니다. 전송이 실패하면 연결된 파이프는 오류 조건이 지워지기 전까지 요청을 처리할 수 없습니다.
제어 전송의 경우 USB 드라이버 스택은 오류 조건을 자동으로 지웁니다. 데이터 전송의 경우 클라이언트는 오류 조건에서 복구하기 위해 적절한 단계를 수행해야 합니다. 데이터 전송에 실패하면 USB 드라이버 스택이 실패한 USBD 상태 코드를 통해 클라이언트 드라이버에 오류를 보고합니다. 상태 코드에 따라 드라이버는 오류 복구 메커니즘을 제공할 수 있습니다.
이 문서에서는 이러한 작업을 통한 오류 복구에 대한 지침을 제공합니다.
- USB 파이프 다시 설정
- 디바이스가 연결된 USB 포트 다시 설정
- USB 포트를 순환하여 클라이언트 드라이버에 대한 디바이스 스택 다시 열거
오류 조건을 지우려면 파이프 다시 설정 작업으로 시작하고 필요한 경우에만 초기화 포트 및 사이클 포트와 같은 더 복잡한 작업을 수행합니다.
다양한 복구 메커니즘을 조정하는 방법에 대해 다음을 수행합니다.
클라이언트 드라이버는 복구를 위해 다른 작업을 조정하고 지정된 시간에 하나의 메서드만 사용되는지 확인해야 합니다. 예를 들어 대량 및 인터럽트라는 두 개의 엔드포인트가 있는 디바이스를 고려합니다. 디바이스에 몇 가지 데이터 전송 요청을 보낸 후 드라이버는 대량 파이프에서 요청이 실패한다는 것을 알 수 있습니다. 이러한 오류를 복구하기 위해 드라이버는 대량 파이프를 다시 설정합니다. 그러나 이 작업은 전송 오류를 해결하지 않으며 대량 전송은 계속 실패합니다. 따라서 드라이버는 USB 포트를 다시 설정하라는 요청을 실행합니다. 한편, 인터럽트 파이프에서 전송이 실패하기 시작한 다음 다시 설정 디바이스 요청이 시작됩니다. 인터럽트 전송 실패에서 복구하기 위해 드라이버는 인터럽트 파이프에서 다시 설정 파이프 요청을 실행합니다. 이러한 두 작업이 조정되지 않은 경우 드라이버는 두 파이프의 오류로 인해 두 개의 디바이스 다시 설정 작업을 동시에 시작할 수 있습니다. 이러한 동시 작업은 문제가 될 수 있습니다.
클라이언트 드라이버는 지정된 시간에 드라이버가 하나의 초기화 포트 또는 사이클 포트 작업을 수행하는지 확인해야 합니다. 이러한 작업 중에는 파이프에서 파이프 다시 설정 작업이 진행되지 않아야 하며 드라이버가 새 파이프 다시 설정 요청을 실행해서는 안 됩니다.
알아야 하는 작업
이 문서에서는 KMDF(커널 모드 드라이버 프레임워크)를 사용합니다.
필수 조건
클라이언트 드라이버가 프레임워크 USB 대상 디바이스 개체를 만들었어야 합니다.
Microsoft Visual Studio Professional 2012와 함께 제공되는 USB 템플릿을 사용하는 경우 템플릿 코드는 이러한 작업을 수행합니다. 템플릿 코드는 대상 디바이스 개체에 대한 핸들을 가져오고 디바이스 컨텍스트에 저장합니다.
KMDF 클라이언트 드라이버는 WdfUsbTargetDeviceCreateWithParameters 메서드를 호출하여 WDFUSBDEVICE 핸들을 가져와야 합니다. 자세한 내용은 KMDF(USB 클라이언트 드라이버 코드 구조) 이해의 "디바이스 소스 코드"를 참조하세요.
클라이언트 드라이버에는 프레임워크 대상 파이프 개체에 대한 핸들이 있어야 합니다. 자세한 내용은 USB 파이프를 열거하는 방법을 참조 하세요.
1단계: 오류 조건의 원인 확인
클라이언트 드라이버는 URB(USB 요청 블록)를 사용하여 데이터 전송을 시작합니다. 요청이 완료되면 USB 드라이버 스택은 전송이 성공했는지 또는 실패했는지를 나타내는 USBD 상태 코드를 반환합니다. 실패에서 USBD 코드는 실패 이유를 나타냅니다.
- WdfUsbTargetDeviceSendUrbSynchronously 메서드를 호출하여 URB를 제출한 경우 메서드가 반환된 후 URB 구조의 Hdr.Status 멤버를 확인합니다.
- WdfRequestSend 메서드를 호출하여 URB를 비동기적으로 제출한 경우 EVT_WDF_REQUEST_COMPLETION_ROUTINE URB 상태를 확인합니다. Params 매개 변수는 WDF_REQUEST_COMPLETION_PARAMS 구조를 가리킵니다. USBD 상태 코드를 확인하려면 Usb-UsbdStatus> 멤버를 검사합니다. 코드에 대한 자세한 내용은 USBD_STATUS 참조하세요.
전송 실패는 USBD_STATUS_STALL_PID 또는 USBD_STATUS_BABBLE_DETECTED 같은 디바이스 오류로 인해 발생할 수 있습니다. 호스트 컨트롤러에서 보고한 오류(예: USBD_STATUS_XACT_ERROR)로 인해 발생할 수도 있습니다.
2단계: 디바이스가 포트에 연결되어 있는지 확인
파이프 또는 디바이스를 다시 설정하는 요청을 실행하기 전에 디바이스가 연결되어 있는지 확인합니다. WdfUsbTargetDeviceIsConnectedSynchronous 메서드를 호출하여 디바이스의 연결된 상태를 확인할 수 있습니다.
3단계: 파이프에 대한 보류 중인 모든 전송 취소
파이프 또는 포트를 다시 설정하는 요청을 보내기 전에 USB 드라이버 스택이 아직 완료되지 않은 파이프로 보류 중인 모든 전송 요청을 취소합니다. 다음 방법 중 하나로 요청을 취소할 수 있습니다.
WdfIoTargetStop 메서드를 호출하여 I/O 대상을 중지합니다.
I/O 대상을 중지하려면 먼저 WdfUsbTargetPipeGetIoTarget 메서드를 호출 하여 프레임워크 파이프 개체와 연결된 WDFIOTARGET 핸들을 가져옵니다. 핸들을 사용하여 WdfIoTargetStop을 호출합니다. 호출에서 작업을 WdfIoTargetCancelSentIo(WDF_IO_TARGET_SENT_IO_ACTION 참조)**로 설정하여 USB 드라이버 스택이 완료되지 않은 모든 요청을 취소하도록 프레임워크에 지시합니다. 완료된 요청의 경우 클라이언트 드라이버는 완료 콜백이 프레임워크에서 호출될 때까지 기다려야 합니다.
중단 파이프 요청을 보냅니다. 다음 방법 중 하나를 호출하여 요청을 보낼 수 있습니다.
WdfUsbTargetPipeAbortSynchronousously 메서드를 호출합니다.
호출은 동기적이며 보류 중인 모든 요청이 취소된 후에만 반환됩니다. WdfUsbTargetPipeAbortSynchronousously 는 선택적 Request 매개 변수를 사용합니다. 미리 할당된 프레임워크 요청 개체에 WDFREQUEST 핸들을 전달하는 것이 좋습니다. 매개 변수를 사용하면 드라이버에서 액세스할 수 없는 내부 요청 개체 대신 지정된 요청 개체를 프레임워크에서 사용할 수 있습니다. 이 매개 변수 값을 사용하면 메모리 부족으로 인해 WdfUsbTargetPipeAbortSynchronously 가 실패하지 않습니다.
WdfUsbTargetPipeFormatRequestForAbort 메서드를 호출하여 중단 파이프 요청에 대한 요청 개체의 형식을 지정한 다음, WdfRequestSend 메서드를 호출하여 요청을 보냅니다.
드라이버가 요청을 비동기적으로 보내는 경우 드라이버가 구현하는 드라이버의 EVT_WDF_REQUEST_COMPLETION_ROUTINE 대한 포인터를 지정해야 합니다. 포인터를 지정하려면 WdfRequestSetCompletionRoutine 메서드를 호출합니다.
드라이버는 WdfRequestSend의 요청 옵션 중 하나로 WDF_REQUEST_SEND_OPTION_SYNCHRONOUS 지정하여 요청을 동기적으로 보낼 수 있습니다. 요청을 동기적으로 보내는 경우 대신 WdfUsbTargetPipeAbortSynchronously를 호출합니다.
4단계: USB 파이프 다시 설정
파이프를 다시 설정하여 오류 복구를 시작합니다. 다음 방법 중 하나를 호출하여 다시 설정 파이프 요청을 보낼 수 있습니다.
WdfUsbTargetPipeResetSynchronously를 호출하여 다시 설정 파이프 요청을 동기적으로 보냅니다.
WdfUsbTargetPipeFormatRequestForReset 메서드를 호출하여 다시 설정 파이프 요청에 대한 요청 개체의 형식을 지정한 다음, WdfRequestSend 메서드를 호출하여 요청을 보냅니다. 이러한 호출은 3단계에서 설명한 대로 중단 파이프 요청에 대한 호출과 유사합니다.
참고 항목
파이프 다시 설정 작업이 완료될 때까지 새 전송 요청을 보내지 마세요.
다시 설정 파이프 요청은 디바이스 및 호스트 컨트롤러 하드웨어의 오류 조건을 지웁니다. 디바이스 오류를 지우기 위해 USB 드라이버 스택은 ENDPOINT_HALT 기능 선택기를 사용하여 디바이스에 CLEAR_FEATURE 제어 요청을 보냅니다. 요청에 대한 수신자는 파이프와 연결된 엔드포인트입니다. 등시 파이프에서 오류 조건이 발생한 경우 드라이버 스택은 오류의 경우 등시 엔드포인트가 자동으로 지워지므로 디바이스를 지우는 작업을 수행하지 않습니다.
호스트 컨트롤러 오류를 지우기 위해 드라이버 스택은 파이프의 HALT 상태를 지우고 파이프의 데이터 토글을 0으로 다시 설정합니다.
5단계: USB 포트 다시 설정
파이프 다시 설정 작업으로 오류 조건이 지워지지 않고 데이터 전송이 계속 실패하는 경우 다시 설정 포트 요청을 보냅니다.
디바이스에 대한 모든 전송을 취소합니다. 이렇게 하려면 현재 구성의 모든 파이프를 열거하고 각 파이프에 대해 예약된 보류 중인 요청을 취소합니다.
디바이스의 I/O 대상을 중지합니다.
WdfUsbTargetDeviceGetIoTarget 메서드를 호출하여 프레임워크 대상 디바이스 개체와 연결된 WDFIOTARGET 핸들을 가져옵니다. 그런 다음 WdfIoTargetStop을 호출하고 WDFIOTARGET 핸들을 지정합니다. 호출에서 작업을 WdfIoTargetCancelSentIo (WDF_IO_TARGET_SENT_IO_ACTION)로 설정합니다.
WdfUsbTargetDeviceResetPortSynchronously 메서드를 호출하여 재설정 포트 요청을 보냅니다.
포트 재설정 작업을 수행하면 디바이스가 USB 버스에서 다시 열거됩니다. USB 드라이버 스택은 열거 후 디바이스 구성을 유지합니다. 드라이버 스택은 기존 파이프 핸들이 유효한 상태로 유지되도록 하기 때문에 클라이언트 드라이버는 이전에 가져온 파이프 핸들을 사용할 수 있습니다.
복합 디바이스의 개별 함수는 다시 설정할 수 없습니다. 복합 디바이스의 경우 특정 함수의 클라이언트 드라이버가 다시 설정 포트 요청을 보내면 전체 디바이스가 다시 설정됩니다. USB 디바이스가 상태를 유지 관리하는 경우 해당 포트 재설정 요청은 다른 함수의 클라이언트 드라이버에 영향을 줄 수 있습니다. 따라서 클라이언트 드라이버가 포트를 다시 설정하기 전에 파이프를 다시 설정하려고 시도하는 것이 중요합니다.
6단계: USB 포트 순환
사이클 포트 작업은 디바이스의 연결이 전기적으로 끊어지지 않는다는 점을 제외하고 플러그를 뽑고 포트에 다시 연결하는 디바이스와 비슷합니다. 디바이스의 연결이 끊어지고 소프트웨어에서 다시 연결됩니다. 이 작업을 수행하면 디바이스 재설정 및 열거형이 발생합니다. 결과적으로 PnP 관리자는 디바이스 노드를 다시 빌드합니다.
포트 다시 설정 작업이 오류 조건을 지우지 못하고 데이터 전송이 계속 실패하는 경우 주기 포트 요청을 보냅니다.
디바이스에 대한 모든 전송을 취소합니다. 현재 구성의 각 파이프에 대해 예약된 보류 중인 요청을 취소해야 합니다(3단계 참조).
디바이스의 I/O 대상을 중지합니다.
WdfUsbTargetDeviceGetIoTarget 메서드를 호출하여 프레임워크 대상 디바이스 개체와 연결된 WDFIOTARGET 핸들을 가져옵니다. 그런 다음 WdfIoTargetStop을 호출하고 WDFIOTARGET 핸들을 지정합니다. 호출에서 작업을 WdfIoTargetCancelSentIo (WDF_IO_TARGET_SENT_IO_ACTION)로 설정합니다.
다음 방법 중 하나를 호출하여 주기 포트 요청을 보냅니다.
- WdfUsbTargetDeviceCyclePortSynchronously를 호출하여 주기 포트 요청을 동기적으로 보냅니다.
- WdfUsbTargetDeviceFormatRequestForCyclePort 메서드를 호출하여 주기 포트 요청에 대한 요청 개체의 형식을 지정한 다음, WdfRequestSend 메서드를 호출하여 요청을 보냅니다. 이러한 호출은 3단계에서 설명한 대로 중단 파이프 요청에 대한 호출과 유사합니다.
클라이언트 드라이버는 주기 포트 요청이 완료된 후에만 디바이스로 전송 요청을 보낼 수 있습니다. USB 드라이버 스택이 주기 포트 요청을 처리하는 동안 디바이스 노드가 제거되기 때문입니다.
주기 포트 요청으로 인해 디바이스가 다시 열거됩니다. USB 드라이버 스택은 디바이스의 연결이 끊어졌는지 PnP 관리자에 알릴 수 있습니다. PnP 관리자는 클라이언트 드라이버와 연결된 디바이스 스택을 중단합니다. 드라이버 스택은 디바이스를 재설정하고, USB 버스에서 다시 열거하고, 디바이스가 연결되었음을 PnP 관리자에 알릴 수 있습니다. 그런 다음 PnP 관리자는 USB 디바이스에 대한 디바이스 스택을 다시 빌드합니다.
주기 포트 작업의 결과로 디바이스에 대한 핸들이 열려 있는 모든 애플리케이션은 디바이스 제거 알림을 받습니다(애플리케이션이 이러한 알림에 등록된 경우). 이에 대한 응답으로 애플리케이션은 디바이스 연결이 끊긴 메시지를 사용자에게 보고할 수 있습니다. 사용자 환경에 영향을 주므로 클라이언트 드라이버는 다른 복구 메커니즘이 오류 조건을 해결하지 않는 경우에만 주기 포트 요청을 선택해야 합니다.
6단계에서 설명하는 초기화 포트 작업과 마찬가지로, 복합 디바이스의 경우 주기 포트 작업은 디바이스의 개별 함수가 아닌 전체 디바이스에 영향을 줍니다.