USB 記述子の取得方法 (UWP アプリ)

USB デバイスを操作する主なタスクの 1 つは、USB デバイスに関する情報を取得することです。 すべての USB デバイスは、記述子と呼ばれる複数のデータ構造の形式で情報を提供します。 この記事では、UWP アプリがエンドポイント、インターフェイス、構成、デバイス レベルでデバイスから記述子を取得する方法について説明します。 この記事では以下についても説明します。

  • USB デバイスのレイアウトを理解する
  • 標準 USB 記述子の取得
  • カスタム記述子の取得

重要な API

USB 記述子

USB デバイスは、デバイス記述子と構成記述子という 2 つの主要な記述子でその機能を記述します。

USB デバイスは、USB デバイス全体に関する情報を含むデバイス記述子を提供する必要があります。 デバイスがその記述子を提供しないか、形式が正しくない記述子を提供している場合、Windows はデバイス ドライバーを読み込むことができません。 記述子の最も重要な情報は、デバイスのハードウェア ID (ベンダー ID フィールドと 製品 ID フィールドの組み合わせ) です。 その情報に基づいて、Windows はデバイスのインボックス ドライバーと一致することができます。 もう 1 つの重要な情報は、既定のエンドポイントの最大パケット サイズ (MaxPacketSize0) です。 既定のエンドポイントは、ホストがデバイスに送信して構成するすべての制御要求のターゲットです。

デバイス記述子の長さは固定されています。

USB デバイスは、完全な構成記述子も提供する必要があります。 この記述子の先頭部分は 9 バイトの固定長で、残りはインターフェイスとそれらのインターフェイスがサポートするエンドポイントの数に応じて可変長になります。 固定長部分は、USB 構成に関する情報 (サポートするインターフェイスの数、デバイスがその構成にあるときの消費電力) を提供します。 これらの初期 9 バイトの後に、すべての USB インターフェイスに関する情報を提供する記述子の可変部分が続きます。 各インターフェイスは 1 つ以上のインターフェイス設定で構成され、各設定は一連のエンドポイントで構成されます。 インターフェイス、代替設定、およびエンドポイントの記述子は、可変部分に含まれます。

デバイスのレイアウトの詳細については、「標準 USB 記述子」を参照してください。

開始する前に

  • デバイスを開き、UsbDevice オブジェクトを取得している必要があります。 「USB デバイスへの接続方法 (UWP アプリ)」を参照してください。
  • このトピックに示す完全なコードは、CustomUsbDeviceAccess サンプルの Scenario5_UsbDescriptors ファイルで確認できます。
  • デバイス レイアウトに関する情報を取得します。 Usbview.exe (Windows 8 用 Windows ソフトウェア開発キット (SDK) に含まれています) は、すべての USB コントローラーと、それらに接続されている USB デバイスを参照できるアプリケーションです。 接続されているデバイスごとに、デバイス、構成、インターフェイス、およびエンドポイント記述子を表示して、デバイスの機能について把握できます。

デバイス記述子を取得する方法

UWP アプリは、UsbDevice.DeviceDescriptor プロパティ値を取得することで、以前に取得した UsbDevice オブジェクトからデバイス記述子を取得できます。

このコード例では、デバイス記述子のフィールド値を文字列に設定する方法を示します。

String GetDeviceDescriptorAsString (UsbDevice device)
{
    String content = null;

    var deviceDescriptor = device.DeviceDescriptor;

    content = "Device Descriptor\n"
            + "\nUsb Spec Number : 0x" + deviceDescriptor.BcdUsb.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nMax Packet Size (Endpoint 0) : " + deviceDescriptor.MaxPacketSize0.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nVendor ID : 0x" + deviceDescriptor.IdVendor.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nProduct ID : 0x" + deviceDescriptor.IdProduct.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nDevice Revision : 0x" + deviceDescriptor.BcdDeviceRevision.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nNumber of Configurations : " + deviceDescriptor.NumberOfConfigurations.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

出力を次に示します。

usb device descriptor.

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

前に取得した UsbDevice オブジェクトから構成記述子の固定部分を取得するには、

  1. UsbDevice から UsbConfiguration オブジェクトを取得します。 UsbConfiguration は、デバイスによって定義された最初の USB 構成を表し、基盤となるデバイス ドライバーによって既定でも選択されます。
  2. UsbConfiguration.ConfigurationDescriptor プロパティ値を取得します。

構成記述子の固定部分は、デバイスの電源特性を示します。 たとえば、デバイスがバスまたは外部ソースから電力を引き出しているかどうかを判断できます (UsbConfigurationDescriptor.SelfPowered を参照)。 デバイスがバスから電力を引き出している場合、消費している電力の量 (ミリアンプ 単位) (UsbConfigurationDescriptor.MaxPowerMilliamps を参照)。 また、UsbConfigurationDescriptor.RemoteWakeup 値を取得することで、デバイスがそれ自体またはシステムを低電力状態から復帰させることができるかどうかを判断できます。

このコード例では、文字列内の構成記述子の固定部分を取得する方法を示します。

String GetConfigurationDescriptorAsString(UsbDevice device)
{
    String content = null;

    var usbConfiguration = device.Configuration;
    var configurationDescriptor = usbConfiguration.ConfigurationDescriptor;

    content = "Configuration Descriptor\n"
            + "\nNumber of Interfaces : " + usbConfiguration.UsbInterfaces.Count.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nConfiguration Value : 0x" + configurationDescriptor.ConfigurationValue.ToString("X2", NumberFormatInfo.InvariantInfo)
            + "\nSelf Powered : " + configurationDescriptor.SelfPowered.ToString()
            + "\nRemote Wakeup : " + configurationDescriptor.RemoteWakeup.ToString()
            + "\nMax Power (milliAmps) : " + configurationDescriptor.MaxPowerMilliamps.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

出力を次に示します。

usb configuration descriptor.

インターフェイス記述子を取得する方法

次に、構成の一部である USB インターフェイスに関する情報を取得できます。

USB インターフェイスは、インターフェイス設定のコレクションです。 そのため、インターフェイス全体を記述する記述子はありません。 インターフェイス記述子という用語は、インターフェイス内の設定を記述するデータ構造を示します。

Windows.Devices.Usb 名前空間は、各 USB インターフェイスとそのインターフェイスに含まれるすべてのインターフェイス記述子 (代替設定用) に関する情報を取得するために使用できるオブジェクトを公開します。 および構成記述子の可変長部分からの記述子。

UsbConfiguration からインターフェイス記述子を取得するには、

  1. UsbConfiguration.UsbInterfaces プロパティを取得して、構成内のインターフェイスの配列を取得します。
  2. 各インターフェイス (UsbInterface) について、次の情報を取得します。
    • アクティブで、データを転送できる一括パイプと割り込みパイプ。
    • インターフェイスの代替設定の配列。
    • インターフェイス記述子の配列。

このコード例では、構成のすべての UsbInterface オブジェクトを取得します。 各オブジェクトから、ヘルパー メソッドは代替設定と開いているバルク パイプおよびインターフェイス パイプの数を取得します。 デバイスが複数のインターフェイスをサポートしている場合、各インターフェイスのデバイス クラス、サブクラス、およびプロトコル コードが異なる場合があります。 ただし、代替設定のすべてのインターフェイス記述子で同じコードを指定する必要があります。 この例では、メソッドは最初の設定のインターフェイス記述子からデバイス クラス、サブクラス、およびプロトコル コードを取得して、インターフェイス全体のコードを決定します。

String GetInterfaceDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var interfaces = device.Configuration.UsbInterfaces;

    content = "Interface Descriptors";

        foreach (UsbInterface usbInterface in interfaces)
        {
            // Class/subclass/protocol values from the first interface setting.

            UsbInterfaceDescriptor usbInterfaceDescriptor = usbInterface.InterfaceSettings[0].InterfaceDescriptor;

            content +="\n\nInterface Number: 0x" +usbInterface.InterfaceNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nClass Code: 0x" +usbInterfaceDescriptor.ClassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nSubclass Code: 0x" +usbInterfaceDescriptor.SubclassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nProtocol Code: 0x" +usbInterfaceDescriptor.ProtocolCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of Interface Settings: "+usbInterface.InterfaceSettings.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk In pipes: "+usbInterface.BulkInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk Out pipes: "+usbInterface.BulkOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt In pipes: "+usbInterface.InterruptInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt Out pipes: "+usbInterface.InterruptOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo);
       }

    return content;
}

出力を次に示します。

usb interface descriptor.

エンドポイント記述子を取得する方法

すべての USB エンドポイント (既定の制御エンドポイントを除く) には、エンドポイント記述子が必要です。 特定のエンドポイントのエンドポイント記述子を取得するには、エンドポイントが属するインターフェイスと代替設定を把握している必要があります。

  1. エンドポイントを 含む UsbInterface オブジェクトを取得します。

  2. UsbInterface.InterfaceSettings を取得して、代替設定の配列を取得します。

  3. 配列内で、エンドポイントを使用する設定 (UsbInterfaceSetting) を見つけます。

  4. 各設定内で、バルク記述子配列と割り込み記述子配列を列挙してエンドポイントを見つけます。

    エンドポイント記述子は、次のオブジェクトによって表されます。

デバイスにインターフェイスが 1 つしかない場合は、UsbDevice.DefaultInterface を使用して、このコード例に示すようにインターフェイスを取得できます。 ここで、ヘルパー メソッドは、アクティブなインターフェイス設定のパイプに関連付けられたエンドポイント記述子を文字列に設定します。

private String GetEndpointDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var usbInterface = device.DefaultInterface;
    var bulkInPipes = usbInterface.BulkInPipes;
    var bulkOutPipes = usbInterface.BulkOutPipes;
    var interruptInPipes = usbInterface.InterruptInPipes;
    var interruptOutPipes = usbInterface.InterruptOutPipes;

    content = "Endpoint Descriptors for open pipes";

    // Print Bulk In Endpoint descriptors
    foreach (UsbBulkInPipe bulkInPipe in bulkInPipes)
    {
        var endpointDescriptor = bulkInPipe.EndpointDescriptor;

        content +="\n\nBulk In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Bulk Out Endpoint descriptors
    foreach (UsbBulkOutPipe bulkOutPipe in bulkOutPipes)
    {
        var endpointDescriptor = bulkOutPipe.EndpointDescriptor;

        content +="\n\nBulk Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Interrupt In Endpoint descriptors
    foreach (UsbInterruptInPipe interruptInPipe in interruptInPipes)
    {
        var endpointDescriptor = interruptInPipe.EndpointDescriptor;

        content +="\n\nInterrupt In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    // Print Interrupt Out Endpoint descriptors
    foreach (UsbInterruptOutPipe interruptOutPipe in interruptOutPipes)
    {
        var endpointDescriptor = interruptOutPipe.EndpointDescriptor;

        content +="\n\nInterrupt Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    return content;
}

出力を次に示します。

usb endpoint descriptors.

カスタム記述子を取得する方法

UsbConfigurationUsbInterface、および UsbInterfaceSetting オブジェクトは、それぞれ記述子のプロパティを公開していることに注意してください。 このプロパティ値は、UsbDescriptor オブジェクトによって表される記述子の配列を取得します。 UsbDescriptor オブジェクトを使用すると、アプリはバッファー内の記述子データを取得できます。 UsbDescriptor.DescriptorType プロパティと UsbDescriptor.Length プロパティは、記述子を保持するために必要なバッファーの型と長さを格納します。

注: すべての記述子バッファーの最初の 2 バイトは、記述子の型と長さも示します。

たとえば、UsbConfiguration.Descriptors プロパティは、完全な構成記述子 (固定および可変長部分) の配列を取得します。 その配列の最初の要素は固定長構成記述子 (UsbConfigurationDescriptor と同じ) であり、2 番目の要素は最初の代替設定のインターフェイス記述子です。

同様に、UsbInterface.Descriptors プロパティは、すべてのインターフェイス記述子と関連するエンドポイント記述子の配列を取得します。 UsbInterfaceSetting.Descriptors プロパティは、エンドポイント記述子など、その設定のすべての記述子の配列を取得します。

記述子を取得するこの方法は、アプリがカスタム記述子や、SuperSpeed デバイスのエンドポイント コンパニオン記述子のような他の記述子を取得する場合に便利です。

このコード例では、構成記述子からバッファー内の記述子データを取得する方法を示します。 この例では、構成記述子セットを取得し、そのセットに含まれるすべての記述子を解析します。 記述子ごとに、DataReader オブジェクトを使用してバッファーを読み取り、記述子の長さと型を表示します。 この例に示すように、カスタム記述子を取得できます。

private String GetCustomDescriptorsAsString(UsbDevice device)
{
    String content = null;
    // Descriptor information will be appended to this string and then printed to UI
    content = "Raw Descriptors";

    var configuration = device.Configuration;
    var allRawDescriptors = configuration.Descriptors;

    // Print first 2 bytes of all descriptors within the configuration descriptor
    // because the first 2 bytes are always length and descriptor type
    // the UsbDescriptor's DescriptorType and Length properties, but we will not use these properties
    // in order to demonstrate ReadDescriptorBuffer() and how to parse it.

    foreach (UsbDescriptor descriptor in allRawDescriptors)
    {
        var descriptorBuffer = new Windows.Storage.Streams.Buffer(descriptor.Length);
        descriptor.ReadDescriptorBuffer(descriptorBuffer);

        DataReader reader = DataReader.FromBuffer(descriptorBuffer);

        // USB data is Little Endian according to the USB spec.
        reader.ByteOrder = ByteOrder.LittleEndian;

        // ReadByte has a side effect where it consumes the current byte, so the next ReadByte will read the next character.
        // Putting multiple ReadByte() on the same line (same variable assignment) may cause the bytes to be read out of order.
        var length = reader.ReadByte().ToString("D", NumberFormatInfo.InvariantInfo);
        var type = "0x" + reader.ReadByte().ToString("X2", NumberFormatInfo.InvariantInfo);

        content += "\n\nDescriptor"
                + "\nLength : " + length
                + "\nDescriptorType : " + type;
    }

    return content;
}