USB 구성 설명자
USB 디바이스는 USB 구성이라는 일련의 인터페이스 형태로 해당 기능을 노출합니다. 각 인터페이스는 하나 이상의 대체 설정으로 구성되며 각 대체 설정은 엔드포인트 집합으로 구성됩니다. 이 항목에서는 USB 구성과 관련된 다양한 설명자에 대해 설명합니다.
USB 구성은 구성 설명자에서 설명합니다( USB_CONFIGURATION_DESCRIPTOR 구조 참조). 구성 설명자에는 구성 및 해당 인터페이스, 대체 설정 및 해당 엔드포인트에 대한 정보가 포함됩니다. 각 인터페이스 설명자 또는 대체 설정은 USB_INTERFACE_DESCRIPTOR 구조에 설명되어 있습니다. 구성에서 각 인터페이스 설명자는 인터페이스 및 대체 설정에 대한 모든 엔드포인트 설명자에 의해 메모리에서 수행됩니다. 각 엔드포인트 설명자는 USB_ENDPOINT_DESCRIPTOR 구조에 저장됩니다.
예를 들어 USB 디바이스 레이아웃에 설명된 USB 웹캠 디바이스를 생각해 보세요. 디바이스는 두 인터페이스가 있는 구성을 지원하고 첫 번째 인터페이스(인덱스 0)는 두 가지 대체 설정을 지원합니다.
다음 예제에서는 USB 웹캠 디바이스에 대한 구성 설명자를 보여줍니다.
Configuration Descriptor:
wTotalLength: 0x02CA
bNumInterfaces: 0x02
bConfigurationValue: 0x01
iConfiguration: 0x00
bmAttributes: 0x80 (Bus Powered )
MaxPower: 0xFA (500 mA)
bConfigurationValue 필드는 디바이스의 펌웨어에 정의된 구성의 수를 나타냅니다. 클라이언트 드라이버는 해당 숫자 값을 사용하여 활성 구성을 선택합니다. USB 디바이스 구성에 대한 자세한 내용은 USB 디바이스에 대한 구성을 선택하는 방법을 참조하세요. USB 구성은 특정 전원 특성도 나타냅니다. bmAttributes에는 구성이 원격 절전 모드 해제 기능을 지원하는지 여부와 디바이스가 버스 구동 또는 자체 전원인지 여부를 나타내는 비트 마스크가 포함되어 있습니다. MaxPower 필드는 디바이스가 버스 전원을 공급할 때 디바이스가 호스트에서 그릴 수 있는 최대 전력(밀리암프 단위)을 지정합니다. 구성 설명자는 디바이스에서 지원하는 총 인터페이스 수(bNumInterfaces)도 나타냅니다.
다음 예제에서는 웹캠 디바이스에 대한 인터페이스 0의 대체 설정 0에 대한 인터페이스 설명자를 보여줍니다.
Interface Descriptor:
bInterfaceNumber: 0x00
bAlternateSetting: 0x00
bNumEndpoints: 0x01
bInterfaceClass: 0x0E
bInterfaceSubClass: 0x02
bInterfaceProtocol: 0x00
iInterface: 0x02
0x0409: "Microsoft LifeCam VX-5000"
0x0409: "Microsoft LifeCam VX-5000"
앞의 예제에서 bInterfaceNumber 및 bAlternateSetting 필드 값을 기록해 둡니다. 이러한 필드에는 클라이언트 드라이버가 인터페이스를 활성화하는 데 사용하는 인덱스 값과 해당 대체 설정 중 하나가 포함됩니다. 활성화를 위해 드라이버는 USB 드라이버 스택에 선택 인터페이스 요청을 보냅니다. 그런 다음 드라이버 스택은 표준 제어 요청(SET INTERFACE)을 빌드하고 디바이스로 보냅니다. bInterfaceClass 필드를 확인합니다. 인터페이스 설명자 또는 대체 설정에 대한 설명자는 클래스 코드, 하위 클래스 및 프로토콜을 지정합니다. 0x0E 값은 인터페이스가 비디오 디바이스 클래스에 대한 것임을 나타냅니다. 또한 iInterface 필드를 확인합니다. 이 값은 인터페이스 설명자에 두 개의 문자열 설명자가 추가되었음을 나타냅니다. 문자열 설명자에는 디바이스 열거 중에 기능을 식별하는 데 사용되는 유니코드 설명이 포함되어 있습니다. 문자열 설명자에 대한 자세한 내용은 USB 문자열 설명자를 참조하세요.
인터페이스의 각 엔드포인트는 디바이스에 대한 단일 입력 또는 출력 스트림을 설명합니다. 다양한 종류의 함수에 대한 스트림을 지원하는 디바이스에는 여러 인터페이스가 있습니다. 함수와 관련된 여러 스트림을 지원하는 디바이스는 단일 인터페이스에서 여러 엔드포인트를 지원할 수 있습니다.
호스트가 엔드포인트에 대한 정보를 가져올 수 있도록 모든 유형의 엔드포인트(기본 엔드포인트 제외)는 엔드포인트 설명자를 제공해야 합니다. 엔드포인트 설명자에는 엔드포인트에서 처리할 수 있는 주소, 형식, 방향 및 데이터 양과 같은 정보가 포함됩니다. 엔드포인트로의 데이터 전송은 해당 정보를 기반으로 합니다.
다음 예제에서는 웹캠 디바이스에 대한 엔드포인트 설명자를 보여줍니다.
Endpoint Descriptor:
bEndpointAddress: 0x82 IN
bmAttributes: 0x01
wMaxPacketSize: 0x0080 (128)
bInterval: 0x01
bEndpointAddress 필드는 엔드포인트 번호(비트 3..0) 및 엔드포인트 방향(비트 7)이 포함된 고유한 엔드포인트 주소를 지정합니다. 앞의 예제에서 이러한 값을 읽으면 설명자가 엔드포인트 번호가 2인 IN 엔드포인트를 설명하는지 확인할 수 있습니다. bmAttributes 특성은 엔드포인트 유형이 등시형임을 나타냅니다. wMaxPacketSizefield는 엔드포인트가 단일 트랜잭션에서 보내거나 받을 수 있는 최대 바이트 수를 나타냅니다. 비트 12..11은 마이크로프레임당 전송할 수 있는 총 트랜잭션 수를 나타냅니다. bInterval은 엔드포인트가 데이터를 보내거나 받을 수 있는 빈도를 나타냅니다.
구성 설명자를 가져오는 방법
구성 설명자는 USB 드라이버 스택에 의해 제어 전송으로 전송되는 표준 디바이스 요청(GET_DESCRIPTOR)을 통해 디바이스에서 가져옵니다. USB 클라이언트 드라이버는 다음 방법 중 하나로 요청을 시작할 수 있습니다.
디바이스가 하나의 구성만 지원하는 경우 가장 쉬운 방법은 프레임워크 제공 WdfUsbTargetDeviceRetrieveConfigDescriptor 메서드를 호출하는 것입니다.
여러 구성을 지원하는 디바이스의 경우 클라이언트 드라이버가 첫 번째 구성 이외의 구성 설명자를 가져오려면 드라이버가 URB를 제출해야 합니다. URB를 제출하려면 드라이버가 URB를 할당하고 포맷한 다음 USB 드라이버 스택에 제출해야 합니다.
URB를 할당하려면 클라이언트 드라이버가 WdfUsbTargetDeviceCreateUrb 메서드를 호출해야 합니다. 메서드는 USB 드라이버 스택에서 할당한 URB에 대한 포인터를 받습니다.
URB의 형식을 지정하기 위해 클라이언트 드라이버는 UsbBuildGetDescriptorRequest 매크로를 사용할 수 있습니다. 매크로는 설명자를 검색할 디바이스 정의 구성 번호와 같이 URB에서 필요한 모든 정보를 설정합니다. URB 함수는 URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE( _URB_CONTROL_DESCRIPTOR_REQUEST 참조)로 설정되고 설명자 형식은 USB_CONFIGURATION_DESCRIPTOR_TYPE. USB 드라이버 스택은 URB에 포함된 정보를 사용하여 표준 제어 요청을 빌드하고 디바이스로 보냅니다.
URB를 제출하려면 클라이언트 드라이버가 WDF 요청 개체를 사용해야 합니다. 요청 개체를 USB 드라이버 스택에 비동기적으로 보내려면 드라이버가 **WdfRequestSend**메서드를 호출해야 합니다. 동기적으로 보내려면 WdfUsbTargetDeviceSendUrbSynchronously 메서드를 호출합니다.
WDM 드라이버: WDM(Windows 드라이버 모델) 클라이언트 드라이버는 URB를 제출해야만 구성 설명자를 가져올 수 있습니다. URB를 할당하려면 드라이버가 USBD_UrbAllocate 루틴을 호출해야 합니다. URB의 서식을 지정하려면 드라이버가 UsbBuildGetDescriptorRequest 매크로를 호출해야 합니다. URB를 제출하려면 드라이버가 URB를 IRP와 연결하고 IRP를 USB 드라이버 스택에 제출해야 합니다. 자세한 내용은 URB를 제출하는 방법을 참조하세요.
USB 구성 내에서 인터페이스 수와 해당 대체 설정은 가변입니다. 따라서 구성 설명자를 보유하는 데 필요한 버퍼 크기를 예측하기가 어렵습니다. 클라이언트 드라이버는 두 단계로 모든 정보를 수집해야 합니다. 먼저 모든 구성 설명자를 보유하는 데 필요한 크기 버퍼를 확인한 다음 전체 설명자를 검색하는 요청을 실행합니다. 클라이언트 드라이버는 다음 방법 중 하나로 크기를 가져올 수 있습니다.
WdfUsbTargetDeviceRetrieveConfigDescriptor를 호출하여 구성 설명자를 가져오려면 다음 단계를 수행합니다.
- WdfUsbTargetDeviceRetrieveConfigDescriptor를 호출하여 모든 구성 정보를 보유하는 데 필요한 버퍼 크기를 가져옵니다. 드라이버는 버퍼에 NULL을 전달하고 버퍼의 크기를 저장할 변수를 전달해야 합니다.
- 이전 WdfUsbTargetDeviceRetrieveConfigDescriptor 호출을 통해 받은 크기에 따라 더 큰 버퍼를 할당합니다.
- WdfUsbTargetDeviceRetrieveConfigDescriptor를 다시 호출하고 2단계에서 할당된 새 버퍼에 대한 포인터를 지정합니다.
NTSTATUS RetrieveDefaultConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = -1;
USHORT sizeConfigDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PAGED_CODE();
*ConfigDescriptor = NULL;
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
NULL,
&sizeConfigDesc);
if (sizeConfigDesc == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
goto Exit;
}
else
{
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
sizeConfigDesc,
USBCLIENT_TAG);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
}
RtlZeroMemory (fullConfigDesc, sizeConfigDesc);
ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
UsbDevice,
fullConfigDesc,
&sizeConfigDesc);
if (!NT_SUCCESS(ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
goto Exit;
}
*ConfigDescriptor = fullConfigDesc;
Exit:
return ntStatus;
}
URB를 제출하여 구성 설명자를 가져오려면 다음 단계를 수행합니다.
- WdfUsbTargetDeviceCreateUrb 메서드를 호출하여 URB를 할당합니다.
- UsbBuildGetDescriptorRequest 매크로를 호출하여 URB의 서식을 지정합니다. URB의 전송 버퍼는 USB_CONFIGURATION_DESCRIPTOR 구조를 보유할 수 있을 만큼 큰 버퍼를 가리킵니다.
- WdfRequestSend 또는 WdfUsbTargetDeviceSendUrbSynchronously를 호출하여 URB를 WDF 요청 개체로 제출합니다.
- 요청이 완료되면 USB_CONFIGURATION_DESCRIPTOR wTotalLength 멤버를 검사. 이 값은 전체 구성 설명자를 포함하는 데 필요한 버퍼의 크기를 나타냅니다.
- wTotalLength에서 검색된 크기에 따라 더 큰 버퍼를 할당합니다.
- 더 큰 버퍼를 사용하여 동일한 요청을 실행합니다.
다음 예제 코드는 i-th 구성에 대한 구성 정보를 가져오는 요청에 대한 UsbBuildGetDescriptorRequest 호출을 보여줍니다.
NTSTATUS FX3_RetrieveConfigurationDescriptor (
_In_ WDFUSBDEVICE UsbDevice,
_In_ PUCHAR ConfigurationIndex,
_Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor
)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
USB_CONFIGURATION_DESCRIPTOR configDesc;
PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;
PURB urb = NULL;
WDFMEMORY urbMemory = NULL;
PAGED_CODE();
RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
*ConfigDescriptor = NULL;
// Allocate an URB for the get-descriptor request.
// WdfUsbTargetDeviceCreateUrb returns the address of the
// newly allocated URB and the WDFMemory object that
// contains the URB.
ntStatus = WdfUsbTargetDeviceCreateUrb (
UsbDevice,
NULL,
&urbMemory,
&urb);
if (!NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not allocate URB for an open-streams request.");
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb, // Points to the URB to be formatted
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ), // Size of the URB.
USB_CONFIGURATION_DESCRIPTOR_TYPE, // Type of descriptor
*ConfigurationIndex, // Index of the configuration
0, // Not used for configuration descriptors
&configDesc, // Points to a USB_CONFIGURATION_DESCRIPTOR structure
NULL, // Not required because we are providing a buffer not MDL
sizeof(USB_CONFIGURATION_DESCRIPTOR), // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
NULL // Reserved.
);
// Send the request synchronously.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if (configDesc.wTotalLength == 0)
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor size.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Allocate memory based on the retrieved size.
// The allocated memory is released by the caller.
fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
NonPagedPool,
configDesc.wTotalLength,
USBCLIENT_TAG);
RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);
if (!fullConfigDesc)
{
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto Exit;
}
// Format the URB.
UsbBuildGetDescriptorRequest (
urb,
(USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),
USB_CONFIGURATION_DESCRIPTOR_TYPE,
*ConfigurationIndex,
0,
fullConfigDesc,
NULL,
configDesc.wTotalLength,
NULL
);
// Send the request again.
ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
UsbDevice,
NULL,
NULL,
urb);
if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not retrieve the configuration descriptor.");
ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;
goto Exit;
}
// Return to the caller.
*ConfigDescriptor = fullConfigDesc;
Exit:
if (urbMemory)
{
WdfObjectDelete (urbMemory);
}
return ntStatus;
}
디바이스가 구성 설명자를 반환하면 요청 버퍼는 모든 대체 설정에 대한 인터페이스 설명자와 특정 대체 설정 내의 모든 엔드포인트에 대한 엔드포인트 설명자로 채워집니다. USB 디바이스 레이아웃에 설명된 디바이스의 경우 다음 다이어그램은 구성 정보가 메모리에 배치되는 방법을 보여 줍니다.
USB_INTERFACE_DESCRIPTOR 0부터 시작하는 bInterfaceNumber 멤버는 구성 내에서 인터페이스를 구분합니다. 지정된 인터페이스의 경우 0부터 시작하는 bAlternateSetting 멤버는 인터페이스의 대체 설정을 구분합니다. 디바이스는 bInterfaceNumber 값 순서로 인터페이스 설명자를 반환한 다음 bAlternateSetting 값 순서로 반환합니다.
구성 내에서 지정된 인터페이스 설명자를 검색하기 위해 클라이언트 드라이버는 USBD_ParseConfigurationDescriptorEx 호출할 수 있습니다. 호출에서 클라이언트 드라이버는 구성 내에서 시작 위치를 제공합니다. 필요에 따라 드라이버는 인터페이스 번호, 대체 설정, 클래스, 서브클래스 또는 프로토콜을 지정할 수 있습니다. 루틴은 다음 일치 인터페이스 설명자에 대한 포인터를 반환합니다.
엔드포인트 또는 문자열 설명자에 대한 구성 설명자를 검사하려면 USBD_ParseDescriptors 루틴을 사용합니다. 호출자는 구성 내에서 시작 위치와 설명자 형식(예: USB_STRING_DESCRIPTOR_TYPE 또는 USB_ENDPOINT_DESCRIPTOR_TYPE)을 제공합니다. 루틴은 다음 일치 설명자에 대한 포인터를 반환합니다.