USB 構成記述子

USB デバイスは、USB 構成と呼ばれる一連のインターフェイスの形式でその機能を公開します。 各インターフェイスは 1 つ以上の代替設定で構成され、各代替設定は一連のエンドポイントで構成されます。 このトピックでは、USB 構成に関連付けられたさまざまな記述子について説明します。

USB 構成は、構成記述子で説明されています (USB_CONFIGURATION_DESCRIPTOR 構造体を参照)。 構成記述子には、構成とそのインターフェイス、代替設定、およびそれらのエンドポイントに関する情報が含まれています。 各インターフェイス記述子または代替設定は、USB_INTERFACE_DESCRIPTOR 構造体で説明されています。 構成では、メモリ内で各インターフェイス記述子の後に、インターフェイスおよび代替設定のすべてのエンドポイント記述子が続きます。 各エンドポイント記述子は、USB_ENDPOINT_DESCRIPTOR 構造体に保存されます。

たとえば、「USB デバイス レイアウト」で説明されている USB Web カメラ デバイスについて考えてみます。 デバイスは 2 つのインターフェイスによる構成をサポートし、最初のインターフェイス (インデックス 0) は 2 つの代替設定をサポートします。

次の例は、USB Web カメラ デバイスの構成記述子を示しています。

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) の合計数も示します。

次の例は、Web カメラ デバイスのインターフェイス 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 フィールド値をメモしています。 これらのフィールドには、クライアント ドライバーがインターフェイスとその代替設定の 1 つをアクティブにするために使用するインデックス値が含まれています。 アクティブ化するために、ドライバーはインターフェイス選択要求を USB ドライバー スタックに送信します。 次に、ドライバー スタックは標準の制御要求 (SET INTERFACE) を作成し、それをデバイスに送信します。 bInterfaceClass フィールドをメモします。 インターフェイス記述子またはその代替設定の記述子は、クラス コード、サブクラス、およびプロトコルを指定します。 値 0x0E は、インターフェイスがビデオ デバイス クラス用であることを示します。 また、iInterface フィールドにも注目してください。 この値は、インターフェイス記述子に 2 つの文字列記述子が追加されていることを示します。 文字列記述子には、デバイスの列挙時に機能を識別するために使用される Unicode の説明が含まれています。 文字列記述子の詳細については、「USB 文字列記述子」を参照してください。

インターフェイス内の各エンドポイントは、デバイスの入力または出力の単一ストリームを記述します。 さまざまな種類の関数のストリームをサポートするデバイスには、複数のインターフェイスがあります。 関数に関連する複数のストリームをサポートするデバイスは、単一のインターフェイスで複数のエンドポイントをサポートできます。

すべての種類のエンドポイント (既定のエンドポイントを除く) は、ホストがエンドポイントに関する情報を取得できるようにエンドポイント記述子を提供する必要があります。 エンドポイント記述子には、そのアドレス、種類、方向、エンドポイントが処理できるデータの量などの情報が含まれます。 エンドポイントへのデータ転送は、その情報に基づいています。

次の例は、Web カメラ デバイスのエンドポイント記述子を示しています。

Endpoint Descriptor:
bEndpointAddress:   0x82  IN
bmAttributes:       0x01
wMaxPacketSize:     0x0080 (128)
bInterval:          0x01

bEndpointAddress フィールドは、エンドポイント番号 (ビット 3..0) とエンドポイントの方向 (ビット 7) を含む一意のエンドポイント アドレスを指定します。 前の例でこれらの値を読み取ることにより、記述子がエンドポイント番号 2 の IN エンドポイントを記述していることがわかります。 bmAttributes 属性は、エンドポイントの種類が等時性であることを示します。 wMaxPacketSizefield は、エンドポイントが 1 つのトランザクションで送受信できる最大バイト数を示します。 ビット 12..11 は、マイクロフレームごとに送信できるトランザクションの合計数を示します。 bInterval は、エンドポイントがデータを送受信できる頻度を示します。

構成記述子を取得する方法

構成記述子は、USB ドライバー スタックによって制御転送として送信される標準デバイス要求 (GET_DESCRIPTOR) を通じてデバイスから取得されます。 USB クライアント ドライバーは、次のいずれかの方法で要求を開始できます。

  • デバイスが 1 つの構成のみをサポートしている場合、最も簡単な方法は、フレームワークによって提供される 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 に設定されます。 URB に含まれる情報を使用して、USB ドライバー スタックは標準の制御要求を作成し、それをデバイスに送信します。

    URB を送信するには、クライアント ドライバーは WDF 要求オブジェクトを使用する必要があります。 要求オブジェクトを USB ドライバー スタックに非同期的に送信するには、ドライバーが **WdfRequestSend** メソッドを呼び出す必要があります。 同期的に送信するには、WdfUsbTargetDeviceSendUrbSynchronously メソッドを呼び出します。

    WDM ドライバー: Windows ドライバー モデル (WDM) クライアント ドライバーは、URB を送信することによってのみ構成記述子を取得できます。 URB を割り当てるには、ドライバーは、USBD_UrbAllocate ルーチンを呼び出す必要があります。 URB の書式を設定するには、ドライバーは UsbBuildGetDescriptorRequest マクロを呼び出す必要があります。 URB を送信するには、ドライバーは URB を IRP に関連付け、IRP を USB ドライバー スタックに送信する必要があります。 詳細については、「URB の送信方法」に関する記事を参照してください。

USB 構成内では、インターフェイスの数とその代替設定は可変です。 したがって、構成記述子を保持するために必要なバッファーのサイズを予測することは困難です。 クライアント ドライバーは、2 つの手順ですべての情報を収集する必要があります。 まず、すべての構成記述子を保持するために必要なバッファーのサイズを決定し、その後、記述子全体を取得する要求を発行します。 クライアント ドライバーは、次のいずれかの方法でサイズを取得できます。

WdfUsbTargetDeviceRetrieveConfigDescriptor を呼び出して構成記述子を取得するには、次の手順を実行します。

  1. WdfUsbTargetDeviceRetrieveConfigDescriptor を呼び出して、すべての構成情報を保持するために必要なバッファーのサイズを取得します。 ドライバーは、バッファーに NULL を渡し、バッファーのサイズを保持する変数を渡す必要があります。
  2. 前の WdfUsbTargetDeviceRetrieveConfigDescriptor 呼び出しで受信したサイズに基づいて、より大きなバッファーを割り当てます。
  3. 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 を送信して構成記述子を取得するには、次の手順を実行します。

  1. WdfUsbTargetDeviceCreateUrb メソッドを呼び出して URB を割り当てます。
  2. UsbBuildGetDescriptorRequest マクロを呼び出して URB の書式を設定します。 URB の転送バッファーは、USB_CONFIGURATION_DESCRIPTOR 構造体を保持するのに十分な大きさのバッファーを指している必要があります。
  3. WdfRequestSend または WdfUsbTargetDeviceSendUrbSynchronously を呼び出して、URB を WDF 要求オブジェクトとして送信します。
  4. 要求が完了したら、USB_CONFIGURATION_DESCRIPTORwTotalLength メンバーをチェックします。 この値は、完全な構成記述子を格納するために必要なバッファーのサイズを示します。
  5. wTotalLength で取得したサイズに基づいて、より大きなバッファーを割り当てます。
  6. より大きなバッファーを使用して同じ要求を発行します。

次のコード例は、i 番目の構成の構成情報を取得する要求の 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 デバイス レイアウト」で説明されているデバイスの場合、次の図は、構成情報がメモリ内でどのようにレイアウトされるかを示しています。

Diagram of a configuration descriptor layout.

USB_INTERFACE_DESCRIPTOR の 0 から始まる bInterfaceNumber メンバーは、構成内のインターフェイスを区別します。 特定のインターフェイスの場合、0 から始まる bAlternateSetting メンバーはインターフェイスの代替設定を区別します。 デバイスは、bInterfaceNumber 値の順序で、次に bAlternateSetting 値の順序でインターフェイス記述子を返します。

クライアント ドライバーは USBD_ParseConfigurationDescriptorEx を呼び出して、構成内で特定のインターフェイス記述子を検索することができます。 呼び出しでは、クライアント ドライバーは、構成内の開始位置を提供します。 オプションで、ドライバーはインターフェイス番号、代替設定、クラス、サブクラス、またはプロトコルを指定できます。 ルーチンは、次に一致するインターフェイス記述子へのポインターを返します。

エンドポイントまたは文字列記述子の構成記述子を調べるには、USBD_ParseDescriptors ルーチンを使用します。 呼び出し元は、構成内の開始位置と、USB_STRING_DESCRIPTOR_TYPE や USB_ENDPOINT_DESCRIPTOR_TYPE などの記述子の種類を提供します。 このルーチンは、次に一致する記述子へのポインターを返します。